package router import ( "net/http" "testing" assert "git.sharkk.net/Go/Assert" ) func handler(t *testing.T, expectedParams []string) Handler { return func(params []string) { assert.Equal(t, len(params), len(expectedParams)) for i, expected := range expectedParams { assert.Equal(t, params[i], expected) } } } func TestRootPath(t *testing.T) { r := New() r.Get("/", handler(t, nil)) h, params, found := r.Lookup("GET", "/") assert.True(t, found) h(params) } func TestStaticPath(t *testing.T) { r := New() r.Get("/users/all", handler(t, nil)) h, params, found := r.Lookup("GET", "/users/all") assert.True(t, found) h(params) } func TestSingleParameter(t *testing.T) { r := New() r.Get("/users/[id]", handler(t, []string{"123"})) h, params, found := r.Lookup("GET", "/users/123") assert.True(t, found) h(params) } func TestMultipleParameters(t *testing.T) { r := New() r.Get("/users/[id]/posts/[postId]", handler(t, []string{"123", "456"})) h, params, found := r.Lookup("GET", "/users/123/posts/456") assert.True(t, found) h(params) } func TestNonExistentPath(t *testing.T) { r := New() r.Get("/users/[id]", handler(t, nil)) _, _, found := r.Lookup("GET", "/posts/123") assert.False(t, found) } func TestWrongMethod(t *testing.T) { r := New() r.Get("/users/[id]", handler(t, nil)) _, _, found := r.Lookup("POST", "/users/123") assert.False(t, found) } func TestTrailingSlash(t *testing.T) { r := New() r.Get("/users/[id]", handler(t, []string{"123"})) h, params, found := r.Lookup("GET", "/users/123/") assert.True(t, found) h(params) } func TestDifferentMethods(t *testing.T) { r := New() r.Get("/test", handler(t, nil)) r.Post("/test", handler(t, nil)) r.Put("/test", handler(t, nil)) r.Patch("/test", handler(t, nil)) r.Delete("/test", handler(t, nil)) methods := []string{"GET", "POST", "PUT", "PATCH", "DELETE"} for _, method := range methods { t.Run(method, func(t *testing.T) { _, _, found := r.Lookup(method, "/test") assert.True(t, found) }) } } func TestWildcardPath(t *testing.T) { r := New() t.Run("simple wildcard", func(t *testing.T) { err := r.Get("/files/*path", handler(t, []string{"docs/report.pdf"})) assert.Nil(t, err) h, params, found := r.Lookup("GET", "/files/docs/report.pdf") assert.True(t, found) h(params) }) t.Run("wildcard with empty path", func(t *testing.T) { err := r.Get("/download/*filepath", handler(t, []string{""})) assert.Nil(t, err) h, params, found := r.Lookup("GET", "/download/") assert.True(t, found) h(params) }) t.Run("wildcard with parameter", func(t *testing.T) { err := r.Get("/users/[id]/*action", handler(t, []string{"123", "settings/profile/avatar"})) assert.Nil(t, err) h, params, found := r.Lookup("GET", "/users/123/settings/profile/avatar") assert.True(t, found) h(params) }) t.Run("multiple wildcards not allowed", func(t *testing.T) { err := r.Get("/api/*version/*path", handler(t, nil)) assert.NotNil(t, err) }) t.Run("non-last wildcard not allowed", func(t *testing.T) { err := r.Get("/api/*version/users", handler(t, nil)) assert.NotNil(t, err) }) } // Benchmarks func BenchmarkRouterLookup(b *testing.B) { r := New() h := func(params []string) {} // Setup routes for benchmarking r.Get("/", h) r.Get("/users/all", h) r.Get("/users/[id]", h) r.Get("/users/[id]/posts/[postId]", h) b.Run("root", func(b *testing.B) { for i := 0; i < b.N; i++ { r.Lookup("GET", "/") } }) b.Run("static", func(b *testing.B) { for i := 0; i < b.N; i++ { r.Lookup("GET", "/users/all") } }) b.Run("single_param", func(b *testing.B) { for i := 0; i < b.N; i++ { r.Lookup("GET", "/users/123") } }) b.Run("multi_param", func(b *testing.B) { for i := 0; i < b.N; i++ { r.Lookup("GET", "/users/123/posts/456") } }) b.Run("not_found", func(b *testing.B) { for i := 0; i < b.N; i++ { r.Lookup("GET", "/nonexistent/path") } }) } func BenchmarkParallelLookup(b *testing.B) { r := New() h := func(params []string) {} r.Get("/users/[id]", h) r.Get("/posts/[id]/comments", h) r.Get("/products/[category]/[id]", h) b.RunParallel(func(pb *testing.PB) { i := 0 for pb.Next() { switch i % 3 { case 0: r.Lookup("GET", "/users/123") case 1: r.Lookup("GET", "/posts/456/comments") case 2: r.Lookup("GET", "/products/electronics/789") } i++ } }) } func BenchmarkWildcardLookup(b *testing.B) { r := New() h := func(params []string) {} // Setup routes for benchmarking r.Get("/files/*path", h) r.Get("/users/[id]/*action", h) b.Run("simple_wildcard", func(b *testing.B) { for i := 0; i < b.N; i++ { r.Lookup("GET", "/files/documents/reports/2024/q1.pdf") } }) b.Run("wildcard_with_param", func(b *testing.B) { for i := 0; i < b.N; i++ { r.Lookup("GET", "/users/123/settings/profile/avatar") } }) } func BenchmarkComparison(b *testing.B) { // Custom router setup customRouter := New() customRouter.Get("/", func(params []string) {}) customRouter.Get("/users/all", func(params []string) {}) customRouter.Get("/users/[id]", func(params []string) {}) customRouter.Get("/users/[id]/posts/[postId]", func(params []string) {}) // Standard mux setup mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {}) mux.HandleFunc("/users/all", func(w http.ResponseWriter, r *http.Request) {}) mux.HandleFunc("/users/", func(w http.ResponseWriter, r *http.Request) {}) // Best equivalent for dynamic routes // Root path b.Run("root_path", func(b *testing.B) { b.Run("custom", func(b *testing.B) { for i := 0; i < b.N; i++ { customRouter.Lookup("GET", "/") } }) b.Run("mux", func(b *testing.B) { req, _ := http.NewRequest("GET", "/", nil) for i := 0; i < b.N; i++ { mux.ServeHTTP(nil, req) } }) }) // Static path b.Run("static_path", func(b *testing.B) { b.Run("custom", func(b *testing.B) { for i := 0; i < b.N; i++ { customRouter.Lookup("GET", "/users/all") } }) b.Run("mux", func(b *testing.B) { req, _ := http.NewRequest("GET", "/users/all", nil) for i := 0; i < b.N; i++ { mux.ServeHTTP(nil, req) } }) }) // Dynamic path b.Run("dynamic_path", func(b *testing.B) { b.Run("custom", func(b *testing.B) { for i := 0; i < b.N; i++ { customRouter.Lookup("GET", "/users/123") } }) b.Run("mux", func(b *testing.B) { req, _ := http.NewRequest("GET", "/users/123", nil) for i := 0; i < b.N; i++ { mux.ServeHTTP(nil, req) } }) }) // Not found b.Run("not_found", func(b *testing.B) { b.Run("custom", func(b *testing.B) { for i := 0; i < b.N; i++ { customRouter.Lookup("GET", "/nonexistent/path") } }) b.Run("mux", func(b *testing.B) { req, _ := http.NewRequest("GET", "/nonexistent/path", nil) for i := 0; i < b.N; i++ { mux.ServeHTTP(nil, req) } }) }) }