438 lines
12 KiB
Go
438 lines
12 KiB
Go
package validation
|
||
|
||
import (
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
func TestValidateEmail(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
email string
|
||
wantErr bool
|
||
}{
|
||
{"valid email", "test@example.com", false},
|
||
{"valid email with subdomain", "user@mail.example.com", false},
|
||
{"empty email", "", true},
|
||
{"invalid format", "invalid-email", true},
|
||
{"missing @", "testexample.com", true},
|
||
{"missing domain", "test@", true},
|
||
{"multiple @ symbols", "test@@example.com", true},
|
||
{"consecutive dots", "test..user@example.com", false},
|
||
{"dot at start", ".test@example.com", false},
|
||
{"dot at end", "test.@example.com", false},
|
||
{"too long", "a" + strings.Repeat("x", 250) + "@example.com", true},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
err := ValidateEmail(tt.email)
|
||
if (err != nil) != tt.wantErr {
|
||
t.Errorf("ValidateEmail() error = %v, wantErr %v", err, tt.wantErr)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestValidateUsername(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
username string
|
||
wantErr bool
|
||
}{
|
||
{"valid username", "testuser", false},
|
||
{"valid with underscore", "test_user", false},
|
||
{"valid with hyphen", "test-user", false},
|
||
{"valid with numbers", "test123", false},
|
||
{"empty username", "", true},
|
||
{"too short", "ab", true},
|
||
{"too long", "a" + strings.Repeat("x", 50), true},
|
||
{"invalid characters", "test@user", true},
|
||
{"spaces", "test user", true},
|
||
{"exactly 3 chars", "abc", false},
|
||
{"exactly 20 chars", strings.Repeat("a", 20), false},
|
||
{"exactly 50 chars", strings.Repeat("a", 50), false},
|
||
{"exactly 51 chars", strings.Repeat("a", 51), true},
|
||
{"starts with number", "123user", false},
|
||
{"starts with underscore", "_user", false},
|
||
{"starts with hyphen", "-user", false},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
err := ValidateUsername(tt.username)
|
||
if (err != nil) != tt.wantErr {
|
||
t.Errorf("ValidateUsername() error = %v, wantErr %v", err, tt.wantErr)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestValidatePassword(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
password string
|
||
wantErr bool
|
||
}{
|
||
{"valid password", "Password123!", false},
|
||
{"valid with special chars", "Pass@123", false},
|
||
{"valid with underscore", "Password123_", false},
|
||
{"valid with hyphen", "Password123-", false},
|
||
{"valid with tilde", "Password123~", false},
|
||
{"valid with backtick", "Password123`", false},
|
||
{"valid with pipe", "Password123|", false},
|
||
{"valid with brackets", "Password123[]", false},
|
||
{"valid with braces", "Password123{}", false},
|
||
{"valid with colon", "Password123:", false},
|
||
{"valid with semicolon", "Password123;", false},
|
||
{"valid with quotes", "Password123\"'", false},
|
||
{"valid with angle brackets", "Password123<>", false},
|
||
{"valid with comma and period", "Password123,.", false},
|
||
{"valid with question mark and slash", "Password123?/", false},
|
||
{"empty password", "", true},
|
||
{"too short", "Pass1!", true},
|
||
{"exactly 7 chars", "Pass12!", true},
|
||
{"exactly 8 chars", "Pass123!", false},
|
||
{"exactly 128 chars", createValidPassword(128), false},
|
||
{"exactly 129 chars", createValidPassword(129), true},
|
||
{"no letters", "12345678!", true},
|
||
{"no numbers", "Password!", true},
|
||
{"no special chars", "Password123", true},
|
||
{"too long", strings.Repeat("a", 130), true},
|
||
{"unicode letters", "Pássw0rd123!", false},
|
||
{"unicode numbers", "Password123!", false},
|
||
{"unicode special", "Password123!", false},
|
||
{"mixed unicode", "Pássw0rd123!", false},
|
||
{"only unicode letters", "Pásswórd", true},
|
||
{"only unicode numbers", "12345678", true},
|
||
{"only unicode special", "!@#$%^&*", true},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
err := ValidatePassword(tt.password)
|
||
if (err != nil) != tt.wantErr {
|
||
t.Errorf("ValidatePassword() error = %v, wantErr %v", err, tt.wantErr)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestValidateTitle(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
title string
|
||
wantErr bool
|
||
}{
|
||
{"valid title", "Test Post Title", false},
|
||
{"empty title", "", true},
|
||
{"too short", "ab", true},
|
||
{"too long", strings.Repeat("a", 201), true},
|
||
{"whitespace only", " ", true},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
err := ValidateTitle(tt.title)
|
||
if (err != nil) != tt.wantErr {
|
||
t.Errorf("ValidateTitle() error = %v, wantErr %v", err, tt.wantErr)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestValidateContent(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
content string
|
||
wantErr bool
|
||
}{
|
||
{"valid content", "This is a valid content", false},
|
||
{"empty content", "", false},
|
||
{"short content", "ab", false},
|
||
{"whitespace only", " ", false},
|
||
{"exactly max length", strings.Repeat("a", 10000), false},
|
||
{"over max length", strings.Repeat("a", 10001), true},
|
||
{"very long content", strings.Repeat("a", 20000), true},
|
||
{"content with newlines", "Line 1\nLine 2\nLine 3", false},
|
||
{"content with special chars", "Content with special chars: !@#$%^&*()", false},
|
||
{"unicode content", "Content with unicode: 你好世界", false},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
err := ValidateContent(tt.content)
|
||
if (err != nil) != tt.wantErr {
|
||
t.Errorf("ValidateContent() error = %v, wantErr %v", err, tt.wantErr)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestValidateURL(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
url string
|
||
wantErr bool
|
||
}{
|
||
{"valid HTTP URL", "http://example.com", false},
|
||
{"valid HTTPS URL", "https://example.com", false},
|
||
{"valid with path", "https://example.com/path", false},
|
||
{"valid with query params", "https://example.com/search?q=test", false},
|
||
{"valid with fragment", "https://example.com/page#section", false},
|
||
{"valid with port", "https://example.com:8080/path", false},
|
||
{"empty URL", "", true},
|
||
{"invalid protocol", "ftp://example.com", true},
|
||
{"no protocol", "example.com", true},
|
||
{"too long", "https://example.com/" + strings.Repeat("a", 2040), true},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
err := ValidateURL(tt.url)
|
||
if (err != nil) != tt.wantErr {
|
||
t.Errorf("ValidateURL() error = %v, wantErr %v", err, tt.wantErr)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestValidateSearchQuery(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
query string
|
||
wantErr bool
|
||
}{
|
||
{"valid query", "search term", false},
|
||
{"empty query", "", false},
|
||
{"short query", "a", false},
|
||
{"whitespace only", " ", false},
|
||
{"exactly max length", strings.Repeat("a", 100), false},
|
||
{"over max length", strings.Repeat("a", 101), true},
|
||
{"very long query", strings.Repeat("a", 200), true},
|
||
{"query with special chars", "search with !@#$%^&*()", false},
|
||
{"unicode query", "search with unicode: 你好世界", false},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
err := ValidateSearchQuery(tt.query)
|
||
if (err != nil) != tt.wantErr {
|
||
t.Errorf("ValidateSearchQuery() error = %v, wantErr %v", err, tt.wantErr)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestSanitizeString(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
input string
|
||
expected string
|
||
}{
|
||
{"normal string", "hello world", "hello world"},
|
||
{"with newlines", "hello\nworld", "helloworld"},
|
||
{"with tabs", "hello\tworld", "helloworld"},
|
||
{"with carriage returns", "hello\rworld", "helloworld"},
|
||
{"with null bytes", "hello\x00world", "helloworld"},
|
||
{"with spaces", " hello world ", "hello world"},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
result := SanitizeString(tt.input)
|
||
if result != tt.expected {
|
||
t.Errorf("SanitizeString() = %v, want %v", result, tt.expected)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestValidateStruct(t *testing.T) {
|
||
type TestStruct struct {
|
||
Username string `validate:"required,min=3,max=20"`
|
||
Email string `validate:"required,email"`
|
||
Age int `validate:"min=18,max=120"`
|
||
URL string `validate:"url"`
|
||
Status string `validate:"oneof=active inactive pending"`
|
||
Optional string `validate:"omitempty,min=1"`
|
||
}
|
||
|
||
t.Run("valid struct", func(t *testing.T) {
|
||
s := TestStruct{
|
||
Username: "testuser",
|
||
Email: "test@example.com",
|
||
Age: 25,
|
||
URL: "https://example.com",
|
||
Status: "active",
|
||
}
|
||
err := ValidateStruct(s)
|
||
if err != nil {
|
||
t.Errorf("ValidateStruct() error = %v, want nil", err)
|
||
}
|
||
})
|
||
|
||
t.Run("missing required field", func(t *testing.T) {
|
||
s := TestStruct{
|
||
Email: "test@example.com",
|
||
Age: 25,
|
||
URL: "https://example.com",
|
||
Status: "active",
|
||
}
|
||
err := ValidateStruct(s)
|
||
if err == nil {
|
||
t.Error("ValidateStruct() expected error, got nil")
|
||
}
|
||
if structErr, ok := err.(*StructValidationError); ok {
|
||
if len(structErr.Errors) == 0 {
|
||
t.Error("Expected validation errors, got none")
|
||
}
|
||
}
|
||
})
|
||
|
||
t.Run("invalid email", func(t *testing.T) {
|
||
s := TestStruct{
|
||
Username: "testuser",
|
||
Email: "invalid-email",
|
||
Age: 25,
|
||
URL: "https://example.com",
|
||
Status: "active",
|
||
}
|
||
err := ValidateStruct(s)
|
||
if err == nil {
|
||
t.Error("ValidateStruct() expected error, got nil")
|
||
}
|
||
})
|
||
|
||
t.Run("invalid min", func(t *testing.T) {
|
||
s := TestStruct{
|
||
Username: "ab",
|
||
Email: "test@example.com",
|
||
Age: 25,
|
||
URL: "https://example.com",
|
||
Status: "active",
|
||
}
|
||
err := ValidateStruct(s)
|
||
if err == nil {
|
||
t.Error("ValidateStruct() expected error, got nil")
|
||
}
|
||
})
|
||
|
||
t.Run("invalid max", func(t *testing.T) {
|
||
s := TestStruct{
|
||
Username: strings.Repeat("a", 21),
|
||
Email: "test@example.com",
|
||
Age: 25,
|
||
URL: "https://example.com",
|
||
Status: "active",
|
||
}
|
||
err := ValidateStruct(s)
|
||
if err == nil {
|
||
t.Error("ValidateStruct() expected error, got nil")
|
||
}
|
||
})
|
||
|
||
t.Run("invalid URL", func(t *testing.T) {
|
||
s := TestStruct{
|
||
Username: "testuser",
|
||
Email: "test@example.com",
|
||
Age: 25,
|
||
URL: "invalid-url",
|
||
Status: "active",
|
||
}
|
||
err := ValidateStruct(s)
|
||
if err == nil {
|
||
t.Error("ValidateStruct() expected error, got nil")
|
||
}
|
||
})
|
||
|
||
t.Run("invalid oneof", func(t *testing.T) {
|
||
s := TestStruct{
|
||
Username: "testuser",
|
||
Email: "test@example.com",
|
||
Age: 25,
|
||
URL: "https://example.com",
|
||
Status: "invalid",
|
||
}
|
||
err := ValidateStruct(s)
|
||
if err == nil {
|
||
t.Error("ValidateStruct() expected error, got nil")
|
||
}
|
||
})
|
||
|
||
t.Run("omitempty with empty value", func(t *testing.T) {
|
||
s := TestStruct{
|
||
Username: "testuser",
|
||
Email: "test@example.com",
|
||
Age: 25,
|
||
URL: "https://example.com",
|
||
Status: "active",
|
||
Optional: "",
|
||
}
|
||
err := ValidateStruct(s)
|
||
if err != nil {
|
||
t.Errorf("ValidateStruct() error = %v, want nil (empty optional field should be allowed)", err)
|
||
}
|
||
})
|
||
|
||
t.Run("omitempty with valid value", func(t *testing.T) {
|
||
s := TestStruct{
|
||
Username: "testuser",
|
||
Email: "test@example.com",
|
||
Age: 25,
|
||
URL: "https://example.com",
|
||
Status: "active",
|
||
Optional: "valid",
|
||
}
|
||
err := ValidateStruct(s)
|
||
if err != nil {
|
||
t.Errorf("ValidateStruct() error = %v, want nil", err)
|
||
}
|
||
})
|
||
|
||
t.Run("pointer to struct", func(t *testing.T) {
|
||
s := &TestStruct{
|
||
Username: "testuser",
|
||
Email: "test@example.com",
|
||
Age: 25,
|
||
URL: "https://example.com",
|
||
Status: "active",
|
||
}
|
||
err := ValidateStruct(s)
|
||
if err != nil {
|
||
t.Errorf("ValidateStruct() error = %v, want nil", err)
|
||
}
|
||
})
|
||
|
||
t.Run("nil pointer", func(t *testing.T) {
|
||
var s *TestStruct = nil
|
||
err := ValidateStruct(s)
|
||
if err != nil {
|
||
t.Errorf("ValidateStruct() error = %v, want nil (nil should be allowed)", err)
|
||
}
|
||
})
|
||
}
|
||
|
||
func createValidPassword(length int) string {
|
||
if length < 8 {
|
||
return "Pass123!"
|
||
}
|
||
|
||
password := make([]byte, length)
|
||
|
||
for i := range length {
|
||
switch i % 4 {
|
||
case 0:
|
||
password[i] = 'P'
|
||
case 1:
|
||
password[i] = 'a'
|
||
case 2:
|
||
password[i] = '1'
|
||
case 3:
|
||
password[i] = '!'
|
||
}
|
||
}
|
||
|
||
return string(password)
|
||
}
|