/
smalloff
/
paramvalidator
Обзор
Документация
Войти
/
smalloff
/
paramvalidator
Код
Задачи
Вики
Пакеты
0
Релизы
1
Аналитика
Безопасность
main
paramvalidator_test.go
1 609 строк
41 KB
smalloff
add support comments
03 окт 2025, 15:06
03 окт 2025, 15:06
d4f6595
Код
Авторство
О чём код?
package paramvalidator import ( "fmt" "strings" "sync" "testing" ) func TestNewParamValidator(t *testing.T) { tests := []struct { name string rulesStr string wantValid bool wantError bool }{ { name: "empty rules", rulesStr: "", wantValid: true, wantError: false, }, { name: "global rules", rulesStr: "page=[1]&sort=[name,date]", wantValid: true, wantError: false, }, { name: "url rules", rulesStr: "/products?page=[1];/users?sort=[name,date]", wantValid: true, wantError: false, }, { name: "invalid rules format", rulesStr: "page=[1", wantValid: false, wantError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pv, err := NewParamValidator(tt.rulesStr) if tt.wantError { if err == nil { t.Errorf("NewParamValidator() expected error for rules %q, but got nil", tt.rulesStr) } if pv != nil { t.Error("NewParamValidator() should return nil validator when error occurs") } } else { if err != nil { t.Errorf("NewParamValidator() unexpected error = %v for rules %q", err, tt.rulesStr) } if pv == nil { t.Error("Expected validator to be created") } } }) } } func TestParseRules(t *testing.T) { pv, err := NewParamValidator("") if err != nil { t.Fatalf("Failed to create validator: %v", err) } tests := []struct { name string rulesStr string wantError bool }{ { name: "empty string", rulesStr: "", wantError: false, }, { name: "valid global rules", rulesStr: "page=[1]&limit=[5,10,20]", wantError: false, }, { name: "valid url rules", rulesStr: "/products?page=[1];/users?limit=[5,10,20]", wantError: false, }, { name: "invalid rules format - unclosed bracket", rulesStr: "page=[1", wantError: true, }, { name: "invalid rules format - empty parameter name", rulesStr: "=[1]", wantError: true, }, { name: "invalid enum values", rulesStr: "page=[a,b,c]", wantError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := pv.ParseRules(tt.rulesStr) if tt.wantError { if err == nil { t.Errorf("ParseRules() expected error for rules %q, but got nil", tt.rulesStr) } } else { if err != nil { t.Errorf("ParseRules() unexpected error = %v for rules %q", err, tt.rulesStr) } } }) } } func TestValidateURL(t *testing.T) { tests := []struct { name string rules string url string expected bool }{ { name: "empty rules - any url invalid", rules: "", url: "/test?param=value", expected: false, }, { name: "allow all params with wildcard", rules: "/api/*?*", url: "/api/test?any=value&other=123", expected: true, }, { name: "valid single value parameter", rules: "/products?page=[5]", url: "/products?page=5", expected: true, }, { name: "invalid single value parameter", rules: "/products?page=[5]", url: "/products?page=10", expected: false, }, { name: "valid enum parameter", rules: "/search?sort=[name,date,price]", url: "/search?sort=name", expected: true, }, { name: "invalid enum parameter", rules: "/search?sort=[name,date,price]", url: "/search?sort=invalid", expected: false, }, { name: "key-only parameter", rules: "/filter?active=[]", url: "/filter?active", expected: true, }, { name: "key-only parameter with value", rules: "/filter?active=[]", url: "/filter?active=true", expected: false, }, { name: "multiple valid parameters", rules: "/api?page=[5]&limit=[10]", url: "/api?page=5&limit=10", expected: true, }, { name: "one invalid parameter", rules: "/api?page=[5]&limit=[10]", url: "/api?page=5&limit=15", expected: false, }, { name: "global parameters", rules: "page=[5]&sort=[name,date]", url: "/any/path?page=5&sort=name", expected: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pv, err := NewParamValidator(tt.rules) if err != nil { t.Fatalf("Failed to create validator: %v", err) } result := pv.ValidateURL(tt.url) if result != tt.expected { t.Errorf("ValidateURL(%q) with rules %q = %v, expected %v", tt.url, tt.rules, result, tt.expected) } }) } } func TestValidateParam(t *testing.T) { pv, err := NewParamValidator("/products?page=[5]&category=[electronics,books]") if err != nil { t.Fatalf("Failed to create validator: %v", err) } tests := []struct { name string urlPath string param string value string expected bool }{ { name: "valid page parameter", urlPath: "/products", param: "page", value: "5", expected: true, }, { name: "invalid page parameter", urlPath: "/products", param: "page", value: "15", expected: false, }, { name: "valid category parameter", urlPath: "/products", param: "category", value: "electronics", expected: true, }, { name: "invalid category parameter", urlPath: "/products", param: "category", value: "invalid", expected: false, }, { name: "unknown parameter", urlPath: "/products", param: "unknown", value: "value", expected: false, }, { name: "wrong url path - should use global rules", urlPath: "/users", param: "page", value: "5", expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := pv.ValidateParam(tt.urlPath, tt.param, tt.value) if result != tt.expected { t.Errorf("ValidateParam(%q, %q, %q) = %v, expected %v", tt.urlPath, tt.param, tt.value, result, tt.expected) } }) } } func TestFilterURL(t *testing.T) { tests := []struct { name string rules string url string expected string }{ { name: "remove invalid parameters", rules: "/search?page=[5]&sort=[name,date]", url: "/search?page=5&sort=name&invalid=value", expected: "/search?page=5&sort=name", }, { name: "filter invalid values - keep valid ones", rules: "/products?page=[5]", url: "/products?page=15&page=5", expected: "/products?page=5", }, { name: "no valid parameters - return path only", rules: "/api?token=[valid]", url: "/api?invalid=value", expected: "/api", }, { name: "allow all parameters with wildcard", rules: "/api/*?*", url: "/api?any=value&other=123", expected: "/api?any=value&other=123", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pv, err := NewParamValidator(tt.rules) if err != nil { t.Fatalf("Failed to create validator: %v", err) } result := pv.FilterURL(tt.url) expected := tt.expected if result != expected { t.Errorf("FilterURL(%q) = %q, expected %q", tt.url, result, expected) } }) } } func TestFilterQuery(t *testing.T) { pv, err := NewParamValidator("/api?page=[5]&limit=[10]") if err != nil { t.Fatalf("Failed to create validator: %v", err) } tests := []struct { name string urlPath string query string expected string }{ { name: "valid parameters", urlPath: "/api", query: "page=5&limit=10", expected: "page=5&limit=10", }, { name: "filter invalid parameters", urlPath: "/api", query: "page=5&limit=15&invalid=value", expected: "page=5", }, { name: "empty query", urlPath: "/api", query: "", expected: "", }, { name: "wrong path - no rules apply", urlPath: "/users", query: "page=5", expected: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := pv.FilterQuery(tt.urlPath, tt.query) if result != tt.expected { t.Errorf("FilterQuery(%q, %q) = %q, expected %q", tt.urlPath, tt.query, result, tt.expected) } }) } } func TestValidateQuery(t *testing.T) { pv, err := NewParamValidator("/api?page=[5]&limit=[10]") if err != nil { t.Fatalf("Failed to create validator: %v", err) } tests := []struct { name string urlPath string query string expected bool }{ { name: "valid parameters", urlPath: "/api", query: "page=5&limit=10", expected: true, }, { name: "invalid parameters", urlPath: "/api", query: "page=5&limit=15", expected: false, }, { name: "empty query", urlPath: "/api", query: "", expected: true, }, { name: "wrong path - no rules apply", urlPath: "/users", query: "page=5", expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := pv.ValidateQuery(tt.urlPath, tt.query) if result != tt.expected { t.Errorf("ValidateQuery(%q, %q) = %v, expected %v", tt.urlPath, tt.query, result, tt.expected) } }) } } func TestClear(t *testing.T) { pv, err := NewParamValidator("/api?page=[5]") if err != nil { t.Fatalf("Failed to create validator: %v", err) } if !pv.ValidateURL("/api?page=5") { t.Error("Should validate before clear") } pv.ClearRules() if pv.ValidateURL("/api?page=5") { t.Error("Should not validate after clear") } } func TestURLPatternMatching(t *testing.T) { tests := []struct { name string rules string url string expected bool }{ { name: "exact match", rules: "/api/v1/users?page=[5]", url: "/api/v1/users?page=5", expected: true, }, { name: "wildcard prefix", rules: "/api/*?page=[5]", url: "/api/v1/users?page=5", expected: true, }, { name: "wildcard suffix", rules: "/api/v1/*?page=[5]", url: "/api/v1/users/list?page=5", expected: true, }, { name: "no match", rules: "/api/v1/users?page=[5]", url: "/api/v1/products?page=5", expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pv, err := NewParamValidator(tt.rules) if err != nil { t.Fatalf("Failed to create validator: %v", err) } result := pv.ValidateURL(tt.url) if result != tt.expected { t.Errorf("URL pattern matching for %q with rules %q = %v, expected %v", tt.url, tt.rules, result, tt.expected) } }) } } func TestEdgeCases(t *testing.T) { t.Run("invalid URL", func(t *testing.T) { pv, err := NewParamValidator("/test?param=value") if err != nil { t.Fatalf("Failed to create validator: %v", err) } if pv.ValidateURL(":invalid:url:") { t.Error("Invalid URL should not validate") } }) t.Run("empty parameter values", func(t *testing.T) { pv, err := NewParamValidator("/test?param=[value1,value2]") if err != nil { t.Fatalf("Failed to create validator: %v", err) } if pv.ValidateParam("/test", "param", "") { t.Error("Empty value should not validate against enum") } }) } func TestMultipleRulesWithSemicolon(t *testing.T) { tests := []struct { name string rules string url string expected bool }{ { name: "multiple URL rules with semicolon", rules: "/products?page=[5];/users?sort=[name,date];/search?q=[]", url: "/products?page=5", expected: true, }, { name: "second rule in list", rules: "/products?page=[5];/users?sort=[name,date]", url: "/users?sort=name", expected: true, }, { name: "third rule in list", rules: "/products?page=[5];/users?sort=[name,date];/search?q=[]", url: "/search?q", expected: true, }, { name: "mixed global and URL rules", rules: "page=[5];/users?sort=[name,date]", url: "/any/path?page=5", expected: true, }, { name: "global rules work for any URL when mixed", rules: "page=[5];/users?sort=[name,date]", url: "/products?page=5", expected: true, }, { name: "URL-specific rules override global for specific path", rules: "page=[100];/products?page=[5]", url: "/products?page=5", expected: true, }, { name: "URL-specific rules override global - invalid case", rules: "page=[100];/products?page=[5]", url: "/products?page=50", expected: false, }, { name: "multiple rules with wildcards", rules: "/api/*?page=[5];/admin/*?access=[admin,superuser]", url: "/api/v1/users?page=5", expected: true, }, { name: "second wildcard rule", rules: "/api/*?page=[5];/admin/*?access=[admin,superuser]", url: "/admin/users?access=admin", expected: true, }, { name: "complex multiple rules", rules: "/products?category=[electronics,books]&price=[500];/users?role=[admin,user]&status=[active,inactive]", url: "/products?category=electronics&price=500", expected: true, }, { name: "another complex rule", rules: "/products?category=[electronics,books]&price=[500];/users?role=[admin,user]&status=[active,inactive]", url: "/users?role=admin&status=active", expected: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pv, err := NewParamValidator(tt.rules) if err != nil { t.Fatalf("Failed to create validator: %v", err) } result := pv.ValidateURL(tt.url) if result != tt.expected { t.Errorf("ValidateURL(%q) with rules %q = %v, expected %v", tt.url, tt.rules, result, tt.expected) } }) } } func TestMultipleRulesNormalization(t *testing.T) { tests := []struct { name string rules string url string expected string }{ { name: "parameters from multiple matching rules", rules: "/api/v1/*?page=[5];/api/v1/users?limit=[10]", url: "/api/v1/users?page=5&limit=10", expected: "/api/v1/users?page=5&limit=10", }, { name: "same parameter name - more specific wins", rules: "/api/*?page=[100];/api/users?page=[5]", url: "/api/users?page=5", expected: "/api/users?page=5", }, { name: "same parameter name - more specific wins with invalid value", rules: "/api/*?page=[100];/api/users?page=[5]", url: "/api/users?page=50", expected: "/api/users", }, { name: "normalize with multiple rules", rules: "/products?page=[5];/users?sort=[name,date]", url: "/products?page=5&invalid=value", expected: "/products?page=5", }, { name: "normalize with second rule", rules: "/products?page=[5];/users?sort=[name,date]", url: "/users?sort=name&invalid=value", expected: "/users?sort=name", }, { name: "normalize with global and URL rules", rules: "page=[5];/users?sort=[name,date]", url: "/any/path?page=5&invalid=value", expected: "/any/path?page=5", }, { name: "multiple rules with different parameters", rules: "/api/*?page=[5];/api/users?limit=[10]", url: "/api/users?page=5&limit=10&invalid=value", expected: "/api/users?page=5&limit=10", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pv, err := NewParamValidator(tt.rules) if err != nil { t.Fatalf("Failed to create validator: %v", err) } result := pv.FilterURL(tt.url) if result != tt.expected { t.Errorf("FilterURL(%q) with rules %q = %q, expected %q", tt.url, tt.rules, result, tt.expected) } }) } } func TestCallbackPattern(t *testing.T) { callbackFunc := func(key string, value string) bool { switch key { case "token": return value == "valid_token" case "user_id": return len(value) > 0 && len(value) <= 10 case "timestamp": return len(value) == 10 default: return false } } tests := []struct { name string rules string url string expected bool }{ { name: "valid callback parameter", rules: "/api?token=[?]", url: "/api?token=valid_token", expected: true, }, { name: "invalid callback parameter", rules: "/api?token=[?]", url: "/api?token=invalid_token", expected: false, }, { name: "multiple callback parameters", rules: "/auth?token=[?]&user_id=[?]", url: "/auth?token=valid_token&user_id=12345", expected: true, }, { name: "mixed callback and regular rules", rules: "/data?token=[?]&page=[5]", url: "/data?token=valid_token&page=5", expected: true, }, { name: "callback with empty value", rules: "/api?token=[?]", url: "/api?token=", expected: false, }, { name: "callback parameter in global rules", rules: "timestamp=[?]", url: "/any/path?timestamp=1234567890", expected: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pv, err := NewParamValidator(tt.rules, WithCallback(callbackFunc)) if err != nil { t.Fatalf("Failed to create validator: %v", err) } result := pv.ValidateURL(tt.url) if result != tt.expected { t.Errorf("ValidateURL(%q) with rules %q = %v, expected %v", tt.url, tt.rules, result, tt.expected) } }) } } func TestCallbackWithoutFunction(t *testing.T) { pv, err := NewParamValidator("/api?token=[?]") if err != nil { t.Fatalf("Failed to create validator: %v", err) } if pv.ValidateURL("/api?token=any_value") { t.Error("Callback parameter should be invalid when no callback function is set") } callbackFunc := func(key string, value string) bool { return value == "valid" } pv.SetCallback(callbackFunc) if !pv.ValidateURL("/api?token=valid") { t.Error("Callback parameter should be valid after setting callback function") } if pv.ValidateURL("/api?token=invalid") { t.Error("Callback parameter should be invalid for wrong values") } } func TestConcurrentValidation(t *testing.T) { pv, err := NewParamValidator("/api/*?page=[5]&limit=[10]&sort=[name,date]") if err != nil { t.Fatalf("Failed to create validator: %v", err) } numGoroutines := 100 var wg sync.WaitGroup wg.Add(numGoroutines) errorCh := make(chan error, numGoroutines) for i := 0; i < numGoroutines; i++ { go func(id int) { defer wg.Done() pageValue := fmt.Sprintf("%d", (id%5)+1) shouldPass := pageValue == "5" switch id % 4 { case 0: result := pv.ValidateURL(fmt.Sprintf("/api/users?page=%s&limit=10", pageValue)) if result != shouldPass { errorCh <- fmt.Errorf("goroutine %d: URL validation failed for page=%s, expected %v, got %v", id, pageValue, shouldPass, result) } case 1: result := pv.ValidateParam("/api/users", "page", pageValue) if result != shouldPass { errorCh <- fmt.Errorf("goroutine %d: param validation failed for page=%s, expected %v, got %v", id, pageValue, shouldPass, result) } case 2: normalized := pv.FilterURL(fmt.Sprintf("/api/users?page=%s&invalid=value", pageValue)) if shouldPass { if !strings.Contains(normalized, "page="+pageValue) { errorCh <- fmt.Errorf("goroutine %d: normalization failed for valid page=%s: %s", id, pageValue, normalized) } } else { if strings.Contains(normalized, "page="+pageValue) { errorCh <- fmt.Errorf("goroutine %d: normalization failed for invalid page=%s: %s", id, pageValue, normalized) } } case 3: filtered := pv.FilterQuery("/api/users", fmt.Sprintf("page=%s&limit=10&invalid=value", pageValue)) if shouldPass { if !strings.Contains(filtered, "page="+pageValue) || !strings.Contains(filtered, "limit=10") { errorCh <- fmt.Errorf("goroutine %d: filtering failed for valid page=%s: %s", id, pageValue, filtered) } } else { if strings.Contains(filtered, "page="+pageValue) { errorCh <- fmt.Errorf("goroutine %d: filtering failed for invalid page=%s: %s", id, pageValue, filtered) } if !strings.Contains(filtered, "limit=10") { errorCh <- fmt.Errorf("goroutine %d: filtering removed valid limit for page=%s: %s", id, pageValue, filtered) } } } }(i) } wg.Wait() close(errorCh) var errors []error for err := range errorCh { errors = append(errors, err) } if len(errors) > 0 { t.Errorf("Concurrent validation failed with %d errors:", len(errors)) for _, err := range errors { t.Error(err) } } } func TestConcurrentFilterQuery(t *testing.T) { pv, err := NewParamValidator("/api/*?page=[5]&limit=[10]&sort=[name,date]") if err != nil { t.Fatalf("Failed to create validator: %v", err) } numGoroutines := 50 var wg sync.WaitGroup wg.Add(numGoroutines) errorCh := make(chan error, numGoroutines) for i := 0; i < numGoroutines; i++ { go func(id int) { defer wg.Done() pageValue := fmt.Sprintf("%d", (id%5)+1) shouldPass := pageValue == "5" query := fmt.Sprintf("page=%s&limit=10&sort=name&invalid=value&extra=param", pageValue) filtered := pv.FilterQuery("/api/users", query) if shouldPass { if !strings.Contains(filtered, "page="+pageValue) { errorCh <- fmt.Errorf("goroutine %d: valid page parameter filtered out: %s", id, filtered) } if !strings.Contains(filtered, "limit=10") { errorCh <- fmt.Errorf("goroutine %d: valid limit parameter filtered out: %s", id, filtered) } if !strings.Contains(filtered, "sort=name") { errorCh <- fmt.Errorf("goroutine %d: valid sort parameter filtered out: %s", id, filtered) } } else { if strings.Contains(filtered, "page="+pageValue) { errorCh <- fmt.Errorf("goroutine %d: invalid page parameter not filtered: %s", id, filtered) } } if strings.Contains(filtered, "invalid=value") || strings.Contains(filtered, "extra=param") { errorCh <- fmt.Errorf("goroutine %d: invalid parameters not filtered: %s", id, filtered) } }(i) } wg.Wait() close(errorCh) var errors []error for err := range errorCh { errors = append(errors, err) } if len(errors) > 0 { t.Errorf("Concurrent FilterQuery failed with %d errors:", len(errors)) for _, err := range errors { t.Error(err) } } } func TestConcurrentValidateQuery(t *testing.T) { pv, err := NewParamValidator("/api/*?page=[5]&limit=[10]&sort=[name,date]") if err != nil { t.Fatalf("Failed to create validator: %v", err) } numGoroutines := 50 var wg sync.WaitGroup wg.Add(numGoroutines) errorCh := make(chan error, numGoroutines) for i := 0; i < numGoroutines; i++ { go func(id int) { defer wg.Done() pageValue := fmt.Sprintf("%d", (id%5)+1) shouldPass := pageValue == "5" query := fmt.Sprintf("page=%s&limit=10&sort=name", pageValue) result := pv.ValidateQuery("/api/users", query) if shouldPass && !result { errorCh <- fmt.Errorf("goroutine %d: valid query params rejected: page=%s, result=%v", id, pageValue, result) } else if !shouldPass && result { errorCh <- fmt.Errorf("goroutine %d: invalid query params accepted: page=%s, result=%v", id, pageValue, result) } }(i) } wg.Wait() close(errorCh) var errors []error for err := range errorCh { errors = append(errors, err) } if len(errors) > 0 { t.Errorf("Concurrent ValidateQuery failed with %d errors:", len(errors)) for _, err := range errors { t.Error(err) } } } func BenchmarkValidateURL(b *testing.B) { pv, err := NewParamValidator("/api/v1/*?page=[5]&limit=[10]&sort=[name,date]") if err != nil { b.Fatalf("Failed to create validator: %v", err) } url := "/api/v1/users/list?page=5&limit=10&sort=name" b.ResetTimer() for i := 0; i < b.N; i++ { pv.ValidateURL(url) } } func BenchmarkFilterURL(b *testing.B) { pv, err := NewParamValidator("/api/*?page=[5]&limit=[10]") if err != nil { b.Fatalf("Failed to create validator: %v", err) } url := "/api/v1/data?page=5&limit=10&invalid=value&extra=param" b.ResetTimer() for i := 0; i < b.N; i++ { pv.FilterURL(url) } } func BenchmarkFilterQuery(b *testing.B) { pv, err := NewParamValidator("/api/*?page=[5]&limit=[10]") if err != nil { b.Fatalf("Failed to create validator: %v", err) } query := "page=5&limit=10&invalid=value&extra=param" b.ResetTimer() for i := 0; i < b.N; i++ { pv.FilterQuery("/api/data", query) } } func BenchmarkValidateQuery(b *testing.B) { pv, err := NewParamValidator("/api/*?page=[5]&limit=[10]") if err != nil { b.Fatalf("Failed to create validator: %v", err) } query := "page=5&limit=10&invalid=value" b.ResetTimer() for i := 0; i < b.N; i++ { pv.ValidateQuery("/api/data", query) } } func BenchmarkConcurrentValidation(b *testing.B) { pv, err := NewParamValidator("/api/*?page=[5]&limit=[10]&sort=[name,date]") if err != nil { b.Fatalf("Failed to create validator: %v", err) } b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { pv.ValidateURL("/api/users?page=5&limit=10&sort=name") pv.ValidateURL("/api/users?page=3&limit=10&sort=date") pv.ValidateURL("/api/users?page=5&limit=15&sort=name") } }) } func BenchmarkConcurrentNormalization(b *testing.B) { pv, err := NewParamValidator("/api/*?page=[5]&limit=[10]") if err != nil { b.Fatalf("Failed to create validator: %v", err) } b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { pv.FilterURL("/api/users?page=5&limit=10&invalid=value") } }) } func BenchmarkConcurrentFilterQuery(b *testing.B) { pv, err := NewParamValidator("/api/*?page=[5]&limit=[10]") if err != nil { b.Fatalf("Failed to create validator: %v", err) } b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { pv.FilterQuery("/api/users", "page=5&limit=10&invalid=value&extra=param") pv.FilterQuery("/api/users", "page=3&limit=10&test=value") pv.FilterQuery("/api/users", "page=5&limit=15&invalid=data") } }) } func BenchmarkConcurrentValidateQuery(b *testing.B) { pv, err := NewParamValidator("/api/*?page=[5]&limit=[10]&sort=[name,date]") if err != nil { b.Fatalf("Failed to create validator: %v", err) } b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { pv.ValidateQuery("/api/users", "page=5&limit=10&sort=name") pv.ValidateQuery("/api/users", "page=3&limit=10&sort=date") pv.ValidateQuery("/api/users", "page=5&limit=15&sort=name") } }) } func BenchmarkFilterQueryBytes(b *testing.B) { pv, err := NewParamValidator("/api/*?page=[5]&limit=[10]&sort=[name,date]") if err != nil { b.Fatalf("Failed to create validator: %v", err) } urlPath := []byte("/api/data") queryBytes := []byte("page=5&limit=10&sort=name&invalid=value") resultBuf := make([]byte, 0, 256) b.ResetTimer() for i := 0; i < b.N; i++ { result := pv.FilterQueryBytes(urlPath, queryBytes, resultBuf[:0]) if len(result) == 0 { b.Error("Expected non-empty result") } } } func BenchmarkValidateQueryBytes(b *testing.B) { pv, err := NewParamValidator("/api/*?page=[5]&limit=[10]&sort=[name,date]") if err != nil { b.Fatalf("Failed to create validator: %v", err) } urlPath := []byte("/api/data") queryBytes := []byte("page=5&limit=10&sort=name") b.ResetTimer() for i := 0; i < b.N; i++ { result := pv.ValidateQueryBytes(urlPath, queryBytes) if !result { b.Error("Expected true result for valid query") } } } func TestNewlineSeparator(t *testing.T) { tests := []struct { name string rules string url string expected bool }{ { name: "multiple URL rules with newline", rules: "/products?page=[5]\n/users?sort=[name,date]\n/search?q=[]", url: "/products?page=5", expected: true, }, { name: "second rule in newline list", rules: "/products?page=[5]\n/users?sort=[name,date]", url: "/users?sort=name", expected: true, }, { name: "third rule in newline list", rules: "/products?page=[5]\n/users?sort=[name,date]\n/search?q=[]", url: "/search?q", expected: true, }, { name: "mixed semicolon and newline", rules: "/products?page=[5];/users?sort=[name,date]\n/search?q=[]", url: "/search?q", expected: true, }, { name: "newline with spaces", rules: "/products?page=[5] \n /users?sort=[name,date] \n /search?q=[]", url: "/users?sort=date", expected: true, }, { name: "multiple newlines between rules", rules: "/products?page=[5]\n\n/users?sort=[name,date]\n\n/search?q=[]", url: "/search?q", expected: true, }, { name: "global rules with newline", rules: "page=[5]\nlimit=[10,20]", url: "/any/path?page=5&limit=10", expected: true, }, { name: "mixed global and URL rules with newline", rules: "page=[5]\n/users?sort=[name,date]", url: "/users?sort=name&page=5", expected: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pv, err := NewParamValidator(tt.rules) if err != nil { t.Fatalf("Failed to create validator: %v", err) } result := pv.ValidateURL(tt.url) if result != tt.expected { t.Errorf("ValidateURL(%q) with rules %q = %v, expected %v", tt.url, tt.rules, result, tt.expected) } }) } } func TestNewlineSeparatorNormalization(t *testing.T) { tests := []struct { name string rules string url string expected string }{ { name: "normalize with newline rules", rules: "/products?page=[5]\n/users?sort=[name,date]", url: "/products?page=5&invalid=value", expected: "/products?page=5", }, { name: "normalize with second newline rule", rules: "/products?page=[5]\n/users?sort=[name,date]", url: "/users?sort=name&invalid=value", expected: "/users?sort=name", }, { name: "multiple rules with newline - filter invalid", rules: "/api/v1/*?page=[5]\n/api/v1/users?limit=[10]", url: "/api/v1/users?page=5&limit=10&invalid=value", expected: "/api/v1/users?page=5&limit=10", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pv, err := NewParamValidator(tt.rules) if err != nil { t.Fatalf("Failed to create validator: %v", err) } result := pv.FilterURL(tt.url) if result != tt.expected { t.Errorf("FilterURL(%q) with rules %q = %q, expected %q", tt.url, tt.rules, result, tt.expected) } }) } } func TestWindowsNewlineSeparator(t *testing.T) { tests := []struct { name string rules string url string expected bool }{ { name: "Windows newline CRLF", rules: "/products?page=[5]\r\n/users?sort=[name,date]", url: "/products?page=5", expected: true, }, { name: "Windows newline second rule", rules: "/products?page=[5]\r\n/users?sort=[name,date]", url: "/users?sort=name", expected: true, }, { name: "mixed Unix and Windows newlines", rules: "/products?page=[5]\n/users?sort=[name,date]\r\n/search?q=[]", url: "/search?q", expected: true, }, { name: "global rules with Windows newline", rules: "page=[5]\r\nlimit=[10,20]", url: "/any/path?page=5&limit=10", expected: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pv, err := NewParamValidator(tt.rules) if err != nil { t.Fatalf("Failed to create validator: %v", err) } result := pv.ValidateURL(tt.url) if result != tt.expected { t.Errorf("ValidateURL(%q) with rules %q = %v, expected %v", tt.url, tt.rules, result, tt.expected) } }) } } func TestMixedNewlineFormats(t *testing.T) { tests := []struct { name string rules string url string expected bool }{ { name: "mixed Unix and Windows newlines complex", rules: "/api/v1?page=[5]\n/api/v2?limit=[10]\r\n/api/v3?sort=[name]", url: "/api/v2?limit=10", expected: true, }, { name: "Windows newlines with spaces", rules: " /products?page=[5] \r\n /users?sort=[name] \r\n /search?q=[] ", url: "/users?sort=name", expected: true, }, { name: "multiple Windows newlines", rules: "/products?page=[5]\r\n\r\n/users?sort=[name]\r\n\r\n/search?q=[]", url: "/search?q", expected: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pv, err := NewParamValidator(tt.rules) if err != nil { t.Fatalf("Failed to create validator: %v", err) } result := pv.ValidateURL(tt.url) if result != tt.expected { t.Errorf("ValidateURL(%q) with rules %q = %v, expected %v", tt.url, tt.rules, result, tt.expected) } }) } } // Add these tests to the paramvalidator_test.go file func TestCommentsInRules(t *testing.T) { tests := []struct { name string rules string url string expected bool }{ { name: "inline comment after rule", rules: "/api?page=[5] ## this is a comment", url: "/api?page=5", expected: true, }, { name: "inline comment with multiple rules", rules: "/products?page=[5] ## product page rule\n/users?sort=[name] ## user sort rule", url: "/users?sort=name", expected: true, }, { name: "comment on its own line", rules: "## Global rules section\npage=[5]\n## URL rules section\n/api?limit=[10]", url: "/api?limit=10&page=5", expected: true, }, { name: "comment in global parameters", rules: "page=[5] ## page parameter\nlimit=[10,20] ## limit parameter", url: "/any/path?page=5&limit=10", expected: true, }, { name: "multiple comments mixed with rules", rules: "## Start of rules\npage=[5]\n## URL specific rules\n/api?sort=[name]\n## End of rules", url: "/api?sort=name&page=5", expected: true, }, { name: "comment after complex rule", rules: "/search?q=[]&sort=[name,date] ## search with key-only and enum", url: "/search?q&sort=name", expected: true, }, { name: "comment with special characters", rules: "/api?token=[?] ## callback validation for tokens", url: "/api?token=test", expected: false, // callback without function should fail }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pv, err := NewParamValidator(tt.rules) if err != nil { t.Fatalf("Failed to create validator: %v", err) } result := pv.ValidateURL(tt.url) if result != tt.expected { t.Errorf("ValidateURL(%q) with rules %q = %v, expected %v", tt.url, tt.rules, result, tt.expected) } }) } } func TestCommentsWithSemicolonSeparator(t *testing.T) { tests := []struct { name string rules string url string expected bool }{ { name: "comments with semicolon separator", rules: "/api?page=[5]; ## api rules\n/users?sort=[name]; ## user rules\n/search?q=[]", url: "/search?q", expected: true, }, { name: "comment after semicolon", rules: "/products?page=[5]; ## comment after semicolon\n/users?sort=[name]", url: "/users?sort=name", expected: true, }, { name: "mixed comments and semicolons", rules: "## Global section\npage=[5];limit=[10]\n## URL section\n/api?sort=[name]", url: "/any/path?page=5&limit=10", expected: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pv, err := NewParamValidator(tt.rules) if err != nil { t.Fatalf("Failed to create validator: %v", err) } result := pv.ValidateURL(tt.url) if result != tt.expected { t.Errorf("ValidateURL(%q) with rules %q = %v, expected %v", tt.url, tt.rules, result, tt.expected) } }) } } func TestCommentsInFilterURL(t *testing.T) { tests := []struct { name string rules string url string expected string }{ { name: "filter with commented rules", rules: "/api?page=[5] ## only page 5 allowed\nlimit=[10,20] ## limits", url: "/api?page=5&limit=10&invalid=value", expected: "/api?page=5&limit=10", }, { name: "comments in URL rules filtering", rules: "## Product rules\n/products?category=[electronics] ## electronics only\n## User rules\n/users?role=[admin]", url: "/products?category=electronics&invalid=param", expected: "/products?category=electronics", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pv, err := NewParamValidator(tt.rules) if err != nil { t.Fatalf("Failed to create validator: %v", err) } result := pv.FilterURL(tt.url) if result != tt.expected { t.Errorf("FilterURL(%q) with rules %q = %q, expected %q", tt.url, tt.rules, result, tt.expected) } }) } } func TestCommentsEdgeCases(t *testing.T) { tests := []struct { name string rules string wantError bool }{ { name: "only comments - should be valid empty rules", rules: "## Only comments here\n## Another comment", wantError: false, }, { name: "comment with double hash in constraint - should be preserved", rules: "/api?pattern=[value##withhash] ## this is a comment", wantError: false, }, { name: "comment inside brackets - should be part of constraint", rules: "/api?param=[value ## not a comment]", wantError: false, }, { name: "multiple hash symbols", rules: "/api?param=[value] ### triple hash comment", wantError: false, }, { name: "hash inside nested brackets", rules: "/api?filter=[and[eq##hash]] ## comment", wantError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pv, err := NewParamValidator(tt.rules) if tt.wantError { if err == nil { t.Errorf("NewParamValidator() expected error for rules %q, but got nil", tt.rules) } if pv != nil { t.Error("NewParamValidator() should return nil validator when error occurs") } } else { if err != nil { t.Errorf("NewParamValidator() unexpected error = %v for rules %q", err, tt.rules) } // For non-error cases, we expect a validator to be created // but it might have empty rules, which is fine if pv == nil { t.Error("Expected validator to be created") } } }) } } func TestCommentsWithCallback(t *testing.T) { callbackFunc := func(key string, value string) bool { return value == "secret123" } tests := []struct { name string rules string url string expected bool }{ { name: "callback with comment", rules: "/auth?token=[?] ## callback validation", url: "/auth?token=secret123", expected: true, }, { name: "callback with invalid value and comment", rules: "/auth?token=[?] ## only secret123 allowed", url: "/auth?token=wrong", expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pv, err := NewParamValidator(tt.rules, WithCallback(callbackFunc)) if err != nil { t.Fatalf("Failed to create validator: %v", err) } result := pv.ValidateURL(tt.url) if result != tt.expected { t.Errorf("ValidateURL(%q) with rules %q = %v, expected %v", tt.url, tt.rules, result, tt.expected) } }) } } func TestCommentsInComplexRules(t *testing.T) { tests := []struct { name string rules string url string expected bool }{ { name: "complex rules with extensive comments", rules: `## API Version 1 Rules /api/v1/*?page=[1,2,3] ## pagination &limit=[10,20,50] ## page sizes &sort=[name,date] ## sort fields ## Admin Section /admin/*?access=[admin] ## admin access required &action=[view,edit] ## allowed actions ## Public API /public?format=[json,xml] ## response formats &callback=[] ## JSONP support`, url: "/api/v1/users?page=2&limit=20&sort=name", expected: true, }, { name: "mixed rules with Windows line endings and comments", rules: "page=[1] ## global page\r\nlimit=[10] ## global limit\r\n/api?sort=[name] ## api sort", url: "/api?page=1&limit=10&sort=name", expected: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pv, err := NewParamValidator(tt.rules) if err != nil { t.Fatalf("Failed to create validator: %v", err) } result := pv.ValidateURL(tt.url) if result != tt.expected { t.Errorf("ValidateURL(%q) with rules %q = %v, expected %v", tt.url, tt.rules, result, tt.expected) } }) } }