test: add comprehensive Go test suite with ~75% coverage
New test files: - config/config_test.go (100% coverage) - constants/constants_test.go (100% coverage) - httputil/response_test.go (100% coverage) - validation/rules_test.go (91.9% coverage) - middleware/logger_test.go, security_test.go, security_logger_test.go - handlers/errors_test.go Updated documentation: - doc/27-GO-TESTING.md: Complete testing guide - doc/00-GO-DOCUMENTATION-INDEX.md: Added testing section - doc/01-ARCHITECTURE.md: Updated package structure - doc/DECISIONS.md: Added ADR-004 caching decision - PROJECT-MEMORY.md: Added Go testing section
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
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", "<script>alert('xss')</script>", "")
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user