package validation import ( "strconv" "strings" "testing" "time" ) func TestRuleOptional(t *testing.T) { // Optional rule should always return nil result := ruleOptional("field", "", "") if result != nil { t.Error("ruleOptional should always return nil") } result = ruleOptional("field", "value", "") if result != nil { t.Error("ruleOptional should always return nil") } } func TestRuleTrim(t *testing.T) { // Trim rule is a marker, should always return nil result := ruleTrim("field", " value ", "") if result != nil { t.Error("ruleTrim should always return nil") } } func TestRuleSanitize(t *testing.T) { // Sanitize rule is a marker, should always return nil result := ruleSanitize("field", "", "") if result != nil { t.Error("ruleSanitize should always return nil") } } func TestRuleMin(t *testing.T) { tests := []struct { name string field string value string param string hasError bool }{ {"Valid - meets minimum", "msg", "hello", "5", false}, {"Valid - exceeds minimum", "msg", "hello world", "5", false}, {"Invalid - too short", "msg", "hi", "5", true}, {"Invalid - empty", "msg", "", "1", true}, {"Invalid param", "msg", "hello", "invalid", true}, {"UTF-8 aware - valid", "name", "José", "4", false}, {"UTF-8 aware - valid", "name", "日本語", "3", false}, {"UTF-8 aware - invalid", "name", "日", "3", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := ruleMin(tt.field, tt.value, tt.param) if (result != nil) != tt.hasError { t.Errorf("ruleMin(%q, %q, %q) error = %v, wantError %v", tt.field, tt.value, tt.param, result != nil, tt.hasError) } }) } } func TestRuleTiming(t *testing.T) { now := time.Now().Unix() tests := []struct { name string value string param string hasError bool }{ {"Empty value", "", "2:86400", false}, {"Valid timing", strconv.FormatInt(now-10, 10), "2:86400", false}, {"Too quick", strconv.FormatInt(now-1, 10), "2:86400", true}, {"Too old", strconv.FormatInt(now-100000, 10), "2:86400", true}, {"Invalid param format", strconv.FormatInt(now-10, 10), "invalid", true}, {"Invalid min param", strconv.FormatInt(now-10, 10), "abc:100", true}, {"Invalid max param", strconv.FormatInt(now-10, 10), "2:xyz", true}, {"Invalid timestamp", "not_a_number", "2:86400", true}, {"Future timestamp", strconv.FormatInt(now+1000, 10), "2:86400", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := ruleTiming("timestamp", tt.value, tt.param) if (result != nil) != tt.hasError { t.Errorf("ruleTiming(%q, %q) error = %v, wantError %v", tt.value, tt.param, result != nil, tt.hasError) } }) } } func TestFieldError_Error(t *testing.T) { t.Run("With param", func(t *testing.T) { err := FieldError{ Field: "email", Tag: "max", Param: "100", Message: "too long", } errStr := err.Error() if !strings.Contains(errStr, "email") { t.Error("Error should contain field name") } if !strings.Contains(errStr, "max=100") { t.Error("Error should contain tag=param") } }) t.Run("Without param", func(t *testing.T) { err := FieldError{ Field: "email", Tag: "required", Message: "is required", } errStr := err.Error() if !strings.Contains(errStr, "email") { t.Error("Error should contain field name") } if strings.Contains(errStr, "(") { t.Error("Error without param should not contain parentheses") } }) } func TestValidationErrors_HasErrors(t *testing.T) { t.Run("No errors", func(t *testing.T) { var ve ValidationErrors if ve.HasErrors() { t.Error("HasErrors should return false for empty errors") } }) t.Run("Has errors", func(t *testing.T) { ve := ValidationErrors{ {Field: "email", Message: "required"}, } if !ve.HasErrors() { t.Error("HasErrors should return true when errors exist") } }) } func TestValidationErrors_GetFieldErrors(t *testing.T) { ve := ValidationErrors{ {Field: "email", Tag: "required", Message: "required"}, {Field: "email", Tag: "email", Message: "invalid format"}, {Field: "name", Tag: "required", Message: "required"}, } t.Run("Get multiple errors for field", func(t *testing.T) { errors := ve.GetFieldErrors("email") if len(errors) != 2 { t.Errorf("GetFieldErrors(email) returned %d errors, want 2", len(errors)) } }) t.Run("Get single error for field", func(t *testing.T) { errors := ve.GetFieldErrors("name") if len(errors) != 1 { t.Errorf("GetFieldErrors(name) returned %d errors, want 1", len(errors)) } }) t.Run("No errors for field", func(t *testing.T) { errors := ve.GetFieldErrors("nonexistent") if len(errors) != 0 { t.Errorf("GetFieldErrors(nonexistent) returned %d errors, want 0", len(errors)) } }) } func TestValidationErrors_Error_Empty(t *testing.T) { var ve ValidationErrors if ve.Error() != "" { t.Error("Error() should return empty string for no errors") } }