472 lines
13 KiB
Go
472 lines
13 KiB
Go
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 <script>", "<script>alert('xss')</script>", true},
|
|
{"HTML tag <img>", "<img src='x'>", true},
|
|
{"HTML tag <div>", "<div>content</div>", true},
|
|
{"HTML tag <a>", "<a href='#'>link</a>", true},
|
|
{"Less than symbol", "5 < 10", false},
|
|
{"Greater than symbol", "10 > 5", false},
|
|
{"Both symbols", "5 < x < 10", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := v.ValidateNoHTML(tt.value, "content")
|
|
if (err != nil) != tt.wantError {
|
|
t.Errorf("ValidateNoHTML() error = %v, wantError %v", err, tt.wantError)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSanitizeString(t *testing.T) {
|
|
v := NewValidator()
|
|
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expected string
|
|
}{
|
|
{"Trim whitespace", " hello ", "hello"},
|
|
{"Remove null bytes", "hello\x00world", "helloworld"},
|
|
{"Already clean", "hello", "hello"},
|
|
{"Empty string", "", ""},
|
|
{"Only whitespace", " ", ""},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := v.SanitizeString(tt.input)
|
|
if result != tt.expected {
|
|
t.Errorf("SanitizeString() = %q, want %q", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStripHTML(t *testing.T) {
|
|
v := NewValidator()
|
|
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expected string
|
|
}{
|
|
{"Remove script tag", "<script>alert('xss')</script>", "alert('xss')"},
|
|
{"Remove div tag", "<div>content</div>", "content"},
|
|
{"Remove multiple tags", "<p>Hello <b>world</b></p>", "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)
|
|
}
|
|
})
|
|
}
|
|
}
|