package router import ( "net/http" "testing" assert "git.sharkk.net/Go/Assert" ) // Create a test handler that implements the Handler interface func testHandler(t *testing.T, expectedParams []string) Handler { return FuncHandler(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("/", testHandler(t, nil)) h, params, found := r.Lookup("GET", "/") assert.True(t, found) h.Serve(params) } func TestStaticPath(t *testing.T) { r := New() r.Get("/users/all", testHandler(t, nil)) h, params, found := r.Lookup("GET", "/users/all") assert.True(t, found) h.Serve(params) } func TestSingleParameter(t *testing.T) { r := New() r.Get("/users/[id]", testHandler(t, []string{"123"})) h, params, found := r.Lookup("GET", "/users/123") assert.True(t, found) h.Serve(params) } func TestMultipleParameters(t *testing.T) { r := New() r.Get("/users/[id]/posts/[postId]", testHandler(t, []string{"123", "456"})) h, params, found := r.Lookup("GET", "/users/123/posts/456") assert.True(t, found) h.Serve(params) } func TestNonExistentPath(t *testing.T) { r := New() r.Get("/users/[id]", testHandler(t, nil)) _, _, found := r.Lookup("GET", "/posts/123") assert.False(t, found) } func TestWrongMethod(t *testing.T) { r := New() r.Get("/users/[id]", testHandler(t, nil)) _, _, found := r.Lookup("POST", "/users/123") assert.False(t, found) } func TestTrailingSlash(t *testing.T) { r := New() r.Get("/users/[id]", testHandler(t, []string{"123"})) h, params, found := r.Lookup("GET", "/users/123/") assert.True(t, found) h.Serve(params) } func TestDifferentMethods(t *testing.T) { r := New() r.Get("/test", testHandler(t, nil)) r.Post("/test", testHandler(t, nil)) r.Put("/test", testHandler(t, nil)) r.Patch("/test", testHandler(t, nil)) r.Delete("/test", testHandler(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", testHandler(t, []string{"docs/report.pdf"})) assert.Nil(t, err) h, params, found := r.Lookup("GET", "/files/docs/report.pdf") assert.True(t, found) h.Serve(params) }) t.Run("wildcard with empty path", func(t *testing.T) { err := r.Get("/download/*filepath", testHandler(t, []string{""})) assert.Nil(t, err) h, params, found := r.Lookup("GET", "/download/") assert.True(t, found) h.Serve(params) }) t.Run("wildcard with parameter", func(t *testing.T) { err := r.Get("/users/[id]/*action", testHandler(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.Serve(params) }) t.Run("multiple wildcards not allowed", func(t *testing.T) { err := r.Get("/api/*version/*path", testHandler(t, nil)) assert.NotNil(t, err) }) t.Run("non-last wildcard not allowed", func(t *testing.T) { err := r.Get("/api/*version/users", testHandler(t, nil)) assert.NotNil(t, err) }) } // Middleware Tests func TestMiddleware(t *testing.T) { t.Run("global middleware", func(t *testing.T) { r := New() // Track middleware execution executed := false r.Use(func(next Handler) Handler { return FuncHandler(func(params []string) { executed = true next.Serve(params) }) }) r.Get("/test", testHandler(t, nil)) h, params, found := r.Lookup("GET", "/test") assert.True(t, found) h.Serve(params) assert.True(t, executed) }) t.Run("multiple middleware", func(t *testing.T) { r := New() // Track middleware execution order order := []int{} r.Use(func(next Handler) Handler { return FuncHandler(func(params []string) { order = append(order, 1) next.Serve(params) order = append(order, 4) }) }) r.Use(func(next Handler) Handler { return FuncHandler(func(params []string) { order = append(order, 2) next.Serve(params) order = append(order, 3) }) }) r.Get("/test", FuncHandler(func(params []string) { order = append(order, 0) })) h, params, found := r.Lookup("GET", "/test") assert.True(t, found) h.Serve(params) // Check middleware execution order (first middleware wraps second) assert.Equal(t, len(order), 5) assert.Equal(t, order[0], 1) // First middleware enter assert.Equal(t, order[1], 2) // Second middleware enter assert.Equal(t, order[2], 0) // Handler assert.Equal(t, order[3], 3) // Second middleware exit assert.Equal(t, order[4], 4) // First middleware exit }) t.Run("route-specific middleware", func(t *testing.T) { r := New() executed := false middleware := func(next Handler) Handler { return FuncHandler(func(params []string) { executed = true next.Serve(params) }) } r.WithMiddleware(middleware).Get("/test", testHandler(t, nil)) h, params, found := r.Lookup("GET", "/test") assert.True(t, found) h.Serve(params) assert.True(t, executed) }) } // Group Tests func TestGroup(t *testing.T) { t.Run("simple group", func(t *testing.T) { r := New() // Create API group api := r.Group("/api") api.Get("/users", testHandler(t, nil)) h, params, found := r.Lookup("GET", "/api/users") assert.True(t, found) h.Serve(params) }) t.Run("nested groups", func(t *testing.T) { r := New() // Create nested groups api := r.Group("/api") v1 := api.Group("/v1") v1.Get("/users", testHandler(t, nil)) h, params, found := r.Lookup("GET", "/api/v1/users") assert.True(t, found) h.Serve(params) }) t.Run("group middleware", func(t *testing.T) { r := New() executed := false // Create group with middleware api := r.Group("/api") api.Use(func(next Handler) Handler { return FuncHandler(func(params []string) { executed = true next.Serve(params) }) }) api.Get("/users", testHandler(t, nil)) h, params, found := r.Lookup("GET", "/api/users") assert.True(t, found) h.Serve(params) assert.True(t, executed) }) t.Run("nested group middleware", func(t *testing.T) { r := New() order := []int{} // Create group with middleware api := r.Group("/api") api.Use(func(next Handler) Handler { return FuncHandler(func(params []string) { order = append(order, 1) next.Serve(params) }) }) // Create nested group with additional middleware v1 := api.Group("/v1") v1.Use(func(next Handler) Handler { return FuncHandler(func(params []string) { order = append(order, 2) next.Serve(params) }) }) v1.Get("/users", FuncHandler(func(params []string) { order = append(order, 3) })) h, params, found := r.Lookup("GET", "/api/v1/users") assert.True(t, found) h.Serve(params) // Check middleware execution order assert.Equal(t, len(order), 3) assert.Equal(t, order[0], 1) // First middleware (from api group) assert.Equal(t, order[1], 2) // Second middleware (from v1 group) assert.Equal(t, order[2], 3) // Handler }) t.Run("route-specific middleware in group", func(t *testing.T) { r := New() order := []int{} // Create group with middleware api := r.Group("/api") api.Use(func(next Handler) Handler { return FuncHandler(func(params []string) { order = append(order, 1) next.Serve(params) }) }) // Add route with specific middleware api.WithMiddleware(func(next Handler) Handler { return FuncHandler(func(params []string) { order = append(order, 2) next.Serve(params) }) }).Get("/users", FuncHandler(func(params []string) { order = append(order, 3) })) h, params, found := r.Lookup("GET", "/api/users") assert.True(t, found) h.Serve(params) // Check middleware execution order assert.Equal(t, len(order), 3) assert.Equal(t, order[0], 1) // Group middleware assert.Equal(t, order[1], 2) // Route-specific middleware assert.Equal(t, order[2], 3) // Handler }) } // Benchmarks func BenchmarkRouterLookup(b *testing.B) { r := New() h := FuncHandler(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 := FuncHandler(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 := FuncHandler(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 BenchmarkMiddleware(b *testing.B) { passthrough := func(next Handler) Handler { return FuncHandler(func(params []string) { next.Serve(params) }) } simpleHandler := FuncHandler(func(params []string) {}) b.Run("no_middleware", func(b *testing.B) { r := New() r.Get("/test", simpleHandler) b.ResetTimer() for i := 0; i < b.N; i++ { h, params, _ := r.Lookup("GET", "/test") h.Serve(params) } }) b.Run("one_middleware", func(b *testing.B) { r := New() r.Use(passthrough) r.Get("/test", simpleHandler) b.ResetTimer() for i := 0; i < b.N; i++ { h, params, _ := r.Lookup("GET", "/test") h.Serve(params) } }) b.Run("five_middleware", func(b *testing.B) { r := New() for i := 0; i < 5; i++ { r.Use(passthrough) } r.Get("/test", simpleHandler) b.ResetTimer() for i := 0; i < b.N; i++ { h, params, _ := r.Lookup("GET", "/test") h.Serve(params) } }) } func BenchmarkGroups(b *testing.B) { simpleHandler := FuncHandler(func(params []string) {}) b.Run("flat_route", func(b *testing.B) { r := New() r.Get("/api/v1/users", simpleHandler) b.ResetTimer() for i := 0; i < b.N; i++ { r.Lookup("GET", "/api/v1/users") } }) b.Run("grouped_route", func(b *testing.B) { r := New() api := r.Group("/api") v1 := api.Group("/v1") v1.Get("/users", simpleHandler) b.ResetTimer() for i := 0; i < b.N; i++ { r.Lookup("GET", "/api/v1/users") } }) b.Run("grouped_route_with_middleware", func(b *testing.B) { r := New() api := r.Group("/api") api.Use(func(next Handler) Handler { return FuncHandler(func(params []string) { next.Serve(params) }) }) v1 := api.Group("/v1") v1.Get("/users", simpleHandler) b.ResetTimer() for i := 0; i < b.N; i++ { h, params, _ := r.Lookup("GET", "/api/v1/users") h.Serve(params) } }) } func BenchmarkComparison(b *testing.B) { // Custom router setup customRouter := New() customRouter.Get("/", FuncHandler(func(params []string) {})) customRouter.Get("/users/all", FuncHandler(func(params []string) {})) customRouter.Get("/users/[id]", FuncHandler(func(params []string) {})) customRouter.Get("/users/[id]/posts/[postId]", FuncHandler(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) } }) }) }