package router_test import ( "strings" "testing" "git.urbach.dev/go/assert" "git.urbach.dev/go/router" "git.urbach.dev/go/router/testdata" ) func TestHello(t *testing.T) { r := router.New[string]() r.Add("GET", "/blog", "Blog") r.Add("GET", "/blog/post", "Blog post") data, params := r.Lookup("GET", "/blog") assert.Equal(t, len(params), 0) assert.Equal(t, data, "Blog") data, params = r.Lookup("GET", "/blog/post") assert.Equal(t, len(params), 0) assert.Equal(t, data, "Blog post") } func TestStatic(t *testing.T) { r := router.New[string]() r.Add("GET", "/hello", "Hello") r.Add("GET", "/world", "World") data, params := r.Lookup("GET", "/hello") assert.Equal(t, len(params), 0) assert.Equal(t, data, "Hello") data, params = r.Lookup("GET", "/world") assert.Equal(t, len(params), 0) assert.Equal(t, data, "World") notFound := []string{ "", "?", "/404", "/hell", "/hall", "/helloo", } for _, path := range notFound { data, params = r.Lookup("GET", path) assert.Equal(t, len(params), 0) assert.Equal(t, data, "") } } func TestParameter(t *testing.T) { r := router.New[string]() r.Add("GET", "/blog/:post", "Blog post") r.Add("GET", "/blog/:post/comments/:id", "Comment") data, params := r.Lookup("GET", "/blog/hello-world") assert.Equal(t, len(params), 1) assert.Equal(t, params[0].Key, "post") assert.Equal(t, params[0].Value, "hello-world") assert.Equal(t, data, "Blog post") data, params = r.Lookup("GET", "/blog/hello-world/comments/123") assert.Equal(t, len(params), 2) assert.Equal(t, params[0].Key, "post") assert.Equal(t, params[0].Value, "hello-world") assert.Equal(t, params[1].Key, "id") assert.Equal(t, params[1].Value, "123") assert.Equal(t, data, "Comment") } func TestMixed(t *testing.T) { r := router.New[string]() r.Add("GET", "/", "Frontpage") r.Add("GET", "/blog", "Blog") r.Add("GET", "/:post", "Post") r.Add("GET", "/sitemap.txt", "Sitemap") data, params := r.Lookup("GET", "/") assert.Equal(t, len(params), 0) assert.Equal(t, data, "Frontpage") data, params = r.Lookup("GET", "/blog") assert.Equal(t, len(params), 0) assert.Equal(t, data, "Blog") data, params = r.Lookup("GET", "/software") assert.Equal(t, len(params), 1) assert.Equal(t, params[0].Key, "post") assert.Equal(t, params[0].Value, "software") assert.Equal(t, data, "Post") data, params = r.Lookup("GET", "/sitemap.txt") assert.Equal(t, len(params), 0) assert.Equal(t, data, "Sitemap") } func TestWildcard(t *testing.T) { r := router.New[string]() r.Add("GET", "/", "Frontpage") r.Add("GET", "/users/:id", "Parameter") r.Add("GET", "/images/static", "Static") r.Add("GET", "/images/*path", "Wildcard") r.Add("GET", "/:post", "Blog post") r.Add("GET", "/*any", "Wildcard") r.Add("GET", "*root", "Root wildcard") data, params := r.Lookup("GET", "/") assert.Equal(t, len(params), 0) assert.Equal(t, data, "Frontpage") data, params = r.Lookup("GET", "/blog-post") assert.Equal(t, len(params), 1) assert.Equal(t, params[0].Key, "post") assert.Equal(t, params[0].Value, "blog-post") assert.Equal(t, data, "Blog post") data, params = r.Lookup("GET", "/users/42") assert.Equal(t, len(params), 1) assert.Equal(t, params[0].Key, "id") assert.Equal(t, params[0].Value, "42") assert.Equal(t, data, "Parameter") data, _ = r.Lookup("GET", "/users/42/test.txt") assert.Equal(t, data, "Wildcard") data, params = r.Lookup("GET", "/images/static") assert.Equal(t, len(params), 0) assert.Equal(t, data, "Static") data, params = r.Lookup("GET", "/images/ste") assert.Equal(t, len(params), 1) assert.Equal(t, params[0].Key, "path") assert.Equal(t, params[0].Value, "ste") assert.Equal(t, data, "Wildcard") data, params = r.Lookup("GET", "/images/sta") assert.Equal(t, len(params), 1) assert.Equal(t, params[0].Key, "path") assert.Equal(t, params[0].Value, "sta") assert.Equal(t, data, "Wildcard") data, params = r.Lookup("GET", "/images/favicon/256.png") assert.Equal(t, len(params), 1) assert.Equal(t, params[0].Key, "path") assert.Equal(t, params[0].Value, "favicon/256.png") assert.Equal(t, data, "Wildcard") data, params = r.Lookup("GET", "not-a-path") assert.Equal(t, len(params), 1) assert.Equal(t, params[0].Key, "root") assert.Equal(t, params[0].Value, "not-a-path") assert.Equal(t, data, "Root wildcard") } func TestMap(t *testing.T) { r := router.New[string]() r.Add("GET", "/hello", "Hello") r.Add("GET", "/world", "World") r.Add("GET", "/user/:user", "User") r.Add("GET", "/*path", "Path") r.Add("GET", "*root", "Root") r.Map(func(data string) string { return strings.Repeat(data, 2) }) data, params := r.Lookup("GET", "/hello") assert.Equal(t, len(params), 0) assert.Equal(t, data, "HelloHello") data, params = r.Lookup("GET", "/world") assert.Equal(t, len(params), 0) assert.Equal(t, data, "WorldWorld") data, params = r.Lookup("GET", "/user/123") assert.Equal(t, len(params), 1) assert.Equal(t, data, "UserUser") data, params = r.Lookup("GET", "/test.txt") assert.Equal(t, len(params), 1) assert.Equal(t, data, "PathPath") data, params = r.Lookup("GET", "test.txt") assert.Equal(t, len(params), 1) assert.Equal(t, data, "RootRoot") } func TestMethods(t *testing.T) { methods := []string{ "GET", "POST", "DELETE", "PUT", "PATCH", "HEAD", "CONNECT", "TRACE", "OPTIONS", } r := router.New[string]() for _, method := range methods { r.Add(method, "/", method) } for _, method := range methods { data, _ := r.Lookup(method, "/") assert.Equal(t, data, method) } } func TestGitHub(t *testing.T) { routes := testdata.Routes("testdata/github.txt") r := router.New[string]() for _, route := range routes { r.Add(route.Method, route.Path, "octocat") } for _, route := range routes { data, _ := r.Lookup(route.Method, route.Path) assert.Equal(t, data, "octocat") data = r.LookupNoAlloc(route.Method, route.Path, func(string, string) {}) assert.Equal(t, data, "octocat") } } func TestTrailingSlash(t *testing.T) { r := router.New[string]() r.Add("GET", "/hello", "Hello 1") data, params := r.Lookup("GET", "/hello") assert.Equal(t, len(params), 0) assert.Equal(t, data, "Hello 1") data, params = r.Lookup("GET", "/hello/") assert.Equal(t, len(params), 0) assert.Equal(t, data, "Hello 1") } func TestTrailingSlashOverwrite(t *testing.T) { r := router.New[string]() r.Add("GET", "/hello", "route 1") r.Add("GET", "/hello/", "route 2") r.Add("GET", "/:param", "route 3") r.Add("GET", "/:param/", "route 4") r.Add("GET", "/*any", "route 5") data, params := r.Lookup("GET", "/hello") assert.Equal(t, len(params), 0) assert.Equal(t, data, "route 1") data, params = r.Lookup("GET", "/hello/") assert.Equal(t, len(params), 0) assert.Equal(t, data, "route 2") data, params = r.Lookup("GET", "/param") assert.Equal(t, len(params), 1) assert.Equal(t, data, "route 3") data, params = r.Lookup("GET", "/param/") assert.Equal(t, len(params), 1) assert.Equal(t, data, "route 4") data, _ = r.Lookup("GET", "/wild/card/") assert.Equal(t, data, "route 5") } func TestOverwrite(t *testing.T) { r := router.New[string]() r.Add("GET", "/", "1") r.Add("GET", "/", "2") r.Add("GET", "/", "3") r.Add("GET", "/", "4") r.Add("GET", "/", "5") data, params := r.Lookup("GET", "/") assert.Equal(t, len(params), 0) assert.Equal(t, data, "5") } func TestInvalidMethod(t *testing.T) { defer func() { if recover() == nil { t.FailNow() } }() r := router.New[string]() r.Add("?", "/hello", "Hello") } func TestMemoryUsage(t *testing.T) { escape := func(a any) {} result := testing.Benchmark(func(b *testing.B) { r := router.New[string]() escape(r) }) t.Logf("%d bytes", result.MemBytes) }