package validation
import (
"strings"
"testing"
)
func TestValidateRequired(t *testing.T) {
v := NewValidator()
tests := []struct {
name string
value string
wantError bool
}{
{"Valid non-empty string", "test", false},
{"Empty string", "", true},
{"Whitespace only", " ", true},
{"Tab only", "\t", true},
{"Newline only", "\n", true},
{"Valid with spaces", "hello world", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := v.ValidateRequired(tt.value, "test_field")
if (err != nil) != tt.wantError {
t.Errorf("ValidateRequired() error = %v, wantError %v", err, tt.wantError)
}
})
}
}
func TestValidateLength(t *testing.T) {
v := NewValidator()
tests := []struct {
name string
value string
min int
max int
wantError bool
}{
{"Valid length", "hello", 3, 10, false},
{"Too short", "ab", 3, 10, true},
{"Too long", "hello world this is too long", 3, 10, true},
{"Exact minimum", "abc", 3, 10, false},
{"Exact maximum", "0123456789", 3, 10, false},
{"No maximum (0)", "very long string here", 3, 0, false},
{"Whitespace counted correctly", " test ", 4, 10, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := v.ValidateLength(tt.value, "test_field", tt.min, tt.max)
if (err != nil) != tt.wantError {
t.Errorf("ValidateLength() error = %v, wantError %v", err, tt.wantError)
}
})
}
}
func TestValidateEmail(t *testing.T) {
v := NewValidator()
tests := []struct {
name string
email string
wantError bool
}{
// Valid emails
{"Valid email", "user@example.com", false},
{"Valid email with plus", "user+tag@example.com", false},
{"Valid email with dot", "first.last@example.com", false},
{"Valid email with hyphen", "user-name@example-domain.com", false},
{"Valid email with numbers", "user123@example456.com", false},
{"Valid email with subdomain", "user@sub.example.com", false},
// Invalid emails
{"Empty email", "", true},
{"Whitespace only", " ", true},
{"Missing @", "userexample.com", true},
{"Missing domain", "user@", true},
{"Missing local part", "@example.com", true},
{"No TLD", "user@localhost", true},
{"Consecutive dots in local", "user..name@example.com", true},
{"Leading dot in local", ".user@example.com", true},
{"Trailing dot in local", "user.@example.com", true},
{"Double @", "user@@example.com", true},
{"Spaces in email", "user name@example.com", true},
{"Invalid characters", "user<>@example.com", true},
{"Too long", strings.Repeat("a", 320) + "@example.com", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := v.ValidateEmail(tt.email, "email")
if (err != nil) != tt.wantError {
t.Errorf("ValidateEmail() error = %v, wantError %v", err, tt.wantError)
}
})
}
}
func TestValidateURL(t *testing.T) {
v := NewValidator()
tests := []struct {
name string
url string
wantError bool
}{
// Valid URLs
{"Valid HTTP URL", "http://example.com", false},
{"Valid HTTPS URL", "https://example.com", false},
{"Valid URL with path", "https://example.com/path/to/resource", false},
{"Valid URL with query", "https://example.com?param=value", false},
{"Valid URL with port", "https://example.com:8080", false},
{"Valid URL with subdomain", "https://sub.example.com", false},
// Invalid URLs
{"Empty URL", "", true},
{"Whitespace only", " ", true},
{"Missing scheme", "example.com", true},
{"Invalid scheme", "ftp://example.com", true},
{"Missing host", "https://", true},
{"Invalid characters", "https://exam ple.com", true},
{"Too long", "https://" + strings.Repeat("a", 2048) + ".com", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := v.ValidateURL(tt.url, "url")
if (err != nil) != tt.wantError {
t.Errorf("ValidateURL() error = %v, wantError %v", err, tt.wantError)
}
})
}
}
func TestValidateHTTPSURL(t *testing.T) {
v := NewValidator()
tests := []struct {
name string
url string
wantError bool
}{
{"Valid HTTPS URL", "https://example.com", false},
{"HTTP URL (should fail)", "http://example.com", true},
{"FTP URL (should fail)", "ftp://example.com", true},
{"Invalid URL", "not-a-url", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := v.ValidateHTTPSURL(tt.url, "url")
if (err != nil) != tt.wantError {
t.Errorf("ValidateHTTPSURL() error = %v, wantError %v", err, tt.wantError)
}
})
}
}
func TestValidateDomain(t *testing.T) {
v := NewValidator()
tests := []struct {
name string
domain string
wantError bool
}{
// Valid domains
{"Valid domain", "example.com", false},
{"Valid subdomain", "sub.example.com", false},
{"Valid deep subdomain", "deep.sub.example.com", false},
{"Valid with hyphen", "my-site.example.com", false},
{"Valid with numbers", "site123.example456.com", false},
// Invalid domains
{"Empty domain", "", true},
{"Whitespace only", " ", true},
{"Too short", "a.b", true},
{"Too long", strings.Repeat("a", 254) + ".com", true},
{"Label too long", strings.Repeat("a", 64) + ".example.com", true},
{"No TLD", "localhost", true},
{"Leading hyphen", "-example.com", true},
{"Trailing hyphen", "example-.com", true},
{"Double dot", "example..com", true},
{"Leading dot", ".example.com", true},
{"Trailing dot", "example.com.", true},
{"Underscore", "my_site.example.com", true},
{"Spaces", "my site.example.com", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := v.ValidateDomain(tt.domain, "domain")
if (err != nil) != tt.wantError {
t.Errorf("ValidateDomain() error = %v, wantError %v", err, tt.wantError)
}
})
}
}
func TestValidateSlug(t *testing.T) {
v := NewValidator()
tests := []struct {
name string
slug string
wantError bool
}{
// Valid slugs
{"Valid slug", "my-company", false},
{"Valid slug with numbers", "company123", false},
{"Valid slug all lowercase", "testcompany", false},
{"Valid slug with multiple hyphens", "my-test-company", false},
// Invalid slugs
{"Empty slug", "", true},
{"Whitespace only", " ", true},
{"Too short", "ab", true},
{"Too long", strings.Repeat("a", 64), true},
{"Uppercase letters", "MyCompany", true},
{"Leading hyphen", "-company", true},
{"Trailing hyphen", "company-", true},
{"Double hyphen", "my--company", true},
{"Underscore", "my_company", true},
{"Spaces", "my company", true},
{"Special characters", "my@company", true},
// Reserved slugs
{"Reserved: api", "api", true},
{"Reserved: admin", "admin", true},
{"Reserved: www", "www", true},
{"Reserved: login", "login", true},
{"Reserved: register", "register", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := v.ValidateSlug(tt.slug, "slug")
if (err != nil) != tt.wantError {
t.Errorf("ValidateSlug() error = %v, wantError %v", err, tt.wantError)
}
})
}
}
func TestValidateUUID(t *testing.T) {
v := NewValidator()
tests := []struct {
name string
uuid string
wantError bool
}{
{"Valid UUID v4", "550e8400-e29b-41d4-a716-446655440000", false},
{"Valid UUID v4 lowercase", "123e4567-e89b-42d3-a456-426614174000", false},
{"Empty UUID", "", true},
{"Invalid format", "not-a-uuid", true},
{"Invalid version", "550e8400-e29b-21d4-a716-446655440000", true},
{"Missing hyphens", "550e8400e29b41d4a716446655440000", true},
{"Too short", "550e8400-e29b-41d4-a716", true},
{"With uppercase", "550E8400-E29B-41D4-A716-446655440000", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := v.ValidateUUID(tt.uuid, "id")
if (err != nil) != tt.wantError {
t.Errorf("ValidateUUID() error = %v, wantError %v", err, tt.wantError)
}
})
}
}
func TestValidateEnum(t *testing.T) {
v := NewValidator()
allowedValues := []string{"free", "basic", "pro", "enterprise"}
tests := []struct {
name string
value string
wantError bool
}{
{"Valid: free", "free", false},
{"Valid: basic", "basic", false},
{"Valid: pro", "pro", false},
{"Valid: enterprise", "enterprise", false},
{"Invalid: premium", "premium", true},
{"Invalid: empty", "", true},
{"Invalid: wrong case", "FREE", true},
{"Invalid: typo", "basi", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := v.ValidateEnum(tt.value, "plan_tier", allowedValues)
if (err != nil) != tt.wantError {
t.Errorf("ValidateEnum() error = %v, wantError %v", err, tt.wantError)
}
})
}
}
func TestValidateRange(t *testing.T) {
v := NewValidator()
tests := []struct {
name string
value int
min int
max int
wantError bool
}{
{"Valid within range", 5, 1, 10, false},
{"Valid at minimum", 1, 1, 10, false},
{"Valid at maximum", 10, 1, 10, false},
{"Below minimum", 0, 1, 10, true},
{"Above maximum", 11, 1, 10, true},
{"No maximum (0)", 1000, 1, 0, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := v.ValidateRange(tt.value, "count", tt.min, tt.max)
if (err != nil) != tt.wantError {
t.Errorf("ValidateRange() error = %v, wantError %v", err, tt.wantError)
}
})
}
}
func TestValidateNoHTML(t *testing.T) {
v := NewValidator()
tests := []struct {
name string
value string
wantError bool
}{
{"Plain text", "Hello world", false},
{"Text with punctuation", "Hello, world!", false},
{"HTML tag ", true},
{"HTML tag ", "
", true},
{"HTML tag
Hello world
", "Hello world"}, {"No tags", "plain text", "plain text"}, {"Empty string", "", ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := v.StripHTML(tt.input) if result != tt.expected { t.Errorf("StripHTML() = %q, want %q", result, tt.expected) } }) } } func TestValidateAndSanitizeString(t *testing.T) { v := NewValidator() tests := []struct { name string input string minLen int maxLen int wantValue string wantError bool }{ {"Valid and clean", "hello", 3, 10, "hello", false}, {"Trim and validate", " hello ", 3, 10, "hello", false}, {"Too short after trim", " a ", 3, 10, "", true}, {"Too long", "hello world this is too long", 3, 10, "", true}, {"Empty after trim", " ", 3, 10, "", true}, {"Valid with null byte removed", "hel\x00lo", 3, 10, "hello", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := v.ValidateAndSanitizeString(tt.input, "test_field", tt.minLen, tt.maxLen) if (err != nil) != tt.wantError { t.Errorf("ValidateAndSanitizeString() error = %v, wantError %v", err, tt.wantError) } if !tt.wantError && result != tt.wantValue { t.Errorf("ValidateAndSanitizeString() = %q, want %q", result, tt.wantValue) } }) } } func TestValidatePrintable(t *testing.T) { v := NewValidator() tests := []struct { name string value string wantError bool }{ {"All printable", "Hello World 123!", false}, {"With tabs and newlines", "Hello\tWorld\n", false}, {"With control character", "Hello\x01World", true}, {"With bell character", "Hello\x07", true}, {"Empty string", "", false}, {"Unicode printable", "Hello 世界", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := v.ValidatePrintable(tt.value, "test_field") if (err != nil) != tt.wantError { t.Errorf("ValidatePrintable() error = %v, wantError %v", err, tt.wantError) } }) } }