initial commit
This commit is contained in:
642
pkg/validation/generic_validator_test.go
Normal file
642
pkg/validation/generic_validator_test.go
Normal file
@@ -0,0 +1,642 @@
|
||||
package validation
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewGenericValidator(t *testing.T) {
|
||||
validator := NewGenericValidator()
|
||||
|
||||
if validator == nil {
|
||||
t.Fatal("Expected validator to be created")
|
||||
}
|
||||
|
||||
if validator.errors == nil {
|
||||
t.Fatal("Expected errors map to be initialized")
|
||||
}
|
||||
|
||||
if len(validator.errors) != 0 {
|
||||
t.Fatal("Expected empty errors map")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericValidator_Validate_Required(t *testing.T) {
|
||||
validator := NewGenericValidator()
|
||||
|
||||
schema := Schema{
|
||||
"name": Rule{
|
||||
Field: "name",
|
||||
Required: true,
|
||||
},
|
||||
"email": Rule{
|
||||
Field: "email",
|
||||
Required: true,
|
||||
},
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"name": "John",
|
||||
// email is missing
|
||||
}
|
||||
|
||||
validator.Validate(data, schema)
|
||||
|
||||
if !validator.HasErrors() {
|
||||
t.Fatal("Expected validation errors")
|
||||
}
|
||||
|
||||
errors := validator.GetErrors()
|
||||
if len(errors) != 1 {
|
||||
t.Fatalf("Expected 1 error, got %d", len(errors))
|
||||
}
|
||||
|
||||
if errors["[email]"] != "This field is missing." {
|
||||
t.Fatalf("Expected email error, got: %s", errors["[email]"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericValidator_Validate_Type(t *testing.T) {
|
||||
validator := NewGenericValidator()
|
||||
|
||||
schema := Schema{
|
||||
"age": Rule{
|
||||
Field: "age",
|
||||
Type: "int",
|
||||
},
|
||||
"price": Rule{
|
||||
Field: "price",
|
||||
Type: "float",
|
||||
},
|
||||
"active": Rule{
|
||||
Field: "active",
|
||||
Type: "bool",
|
||||
},
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"age": "not a number",
|
||||
"price": "invalid",
|
||||
"active": "not boolean",
|
||||
}
|
||||
|
||||
validator.Validate(data, schema)
|
||||
|
||||
if !validator.HasErrors() {
|
||||
t.Fatal("Expected validation errors")
|
||||
}
|
||||
|
||||
errors := validator.GetErrors()
|
||||
if len(errors) != 3 {
|
||||
t.Fatalf("Expected 3 errors, got %d", len(errors))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericValidator_Validate_Range(t *testing.T) {
|
||||
validator := NewGenericValidator()
|
||||
|
||||
min := 1.0
|
||||
max := 100.0
|
||||
|
||||
schema := Schema{
|
||||
"score": Rule{
|
||||
Field: "score",
|
||||
Min: &min,
|
||||
Max: &max,
|
||||
},
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"score": 0.5, // below min
|
||||
}
|
||||
|
||||
validator.Validate(data, schema)
|
||||
|
||||
if !validator.HasErrors() {
|
||||
t.Fatal("Expected validation errors")
|
||||
}
|
||||
|
||||
errors := validator.GetErrors()
|
||||
if len(errors) != 1 {
|
||||
t.Fatalf("Expected 1 error, got %d", len(errors))
|
||||
}
|
||||
|
||||
if errors["[score]"] != "این مقدار باید بزرگتر و یا مساوی 1 باشد." {
|
||||
t.Fatalf("Expected range error, got: %s", errors["[score]"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericValidator_Validate_Length(t *testing.T) {
|
||||
validator := NewGenericValidator()
|
||||
|
||||
minLength := 3
|
||||
maxLength := 10
|
||||
|
||||
schema := Schema{
|
||||
"name": Rule{
|
||||
Field: "name",
|
||||
MinLength: &minLength,
|
||||
MaxLength: &maxLength,
|
||||
},
|
||||
"tags": Rule{
|
||||
Field: "tags",
|
||||
MinLength: &minLength,
|
||||
MaxLength: &maxLength,
|
||||
},
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"name": "ab", // too short
|
||||
"tags": []interface{}{"tag1", "tag2"}, // too few
|
||||
}
|
||||
|
||||
validator.Validate(data, schema)
|
||||
|
||||
if !validator.HasErrors() {
|
||||
t.Fatal("Expected validation errors")
|
||||
}
|
||||
|
||||
errors := validator.GetErrors()
|
||||
if len(errors) != 2 {
|
||||
t.Fatalf("Expected 2 errors, got %d", len(errors))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericValidator_Validate_Custom(t *testing.T) {
|
||||
validator := NewGenericValidator()
|
||||
|
||||
schema := Schema{
|
||||
"code": Rule{
|
||||
Field: "code",
|
||||
Custom: func(value interface{}) error {
|
||||
if str, ok := value.(string); ok {
|
||||
if len(str) != 6 {
|
||||
return fmt.Errorf("کد باید 6 کاراکتر باشد.")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"code": "12345", // too short
|
||||
}
|
||||
|
||||
validator.Validate(data, schema)
|
||||
|
||||
if !validator.HasErrors() {
|
||||
t.Fatal("Expected validation errors")
|
||||
}
|
||||
|
||||
errors := validator.GetErrors()
|
||||
if len(errors) != 1 {
|
||||
t.Fatalf("Expected 1 error, got %d", len(errors))
|
||||
}
|
||||
|
||||
if errors["[code]"] != "کد باید 6 کاراکتر باشد." {
|
||||
t.Fatalf("Expected custom error, got: %s", errors["[code]"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericValidator_ValidateNested(t *testing.T) {
|
||||
validator := NewGenericValidator()
|
||||
|
||||
schema := Schema{
|
||||
"name": Rule{
|
||||
Field: "name",
|
||||
Required: true,
|
||||
},
|
||||
"age": Rule{
|
||||
Field: "age",
|
||||
Type: "int",
|
||||
},
|
||||
}
|
||||
|
||||
nestedData := map[string]interface{}{
|
||||
"users": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "John",
|
||||
"age": "not a number",
|
||||
},
|
||||
map[string]interface{}{
|
||||
// name is missing
|
||||
"age": 25,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
validator.ValidateNested(nestedData["users"], schema, "[users]")
|
||||
|
||||
if !validator.HasErrors() {
|
||||
t.Fatal("Expected validation errors")
|
||||
}
|
||||
|
||||
errors := validator.GetErrors()
|
||||
if len(errors) != 3 {
|
||||
t.Fatalf("Expected 3 errors, got %d", len(errors))
|
||||
}
|
||||
|
||||
// Check for expected errors
|
||||
expectedErrors := map[string]bool{
|
||||
"[users][0][age]": true, // age is string instead of int
|
||||
"[users][1][name]": true, // name is missing (required)
|
||||
"[users][1][age]": true, // age is int (valid)
|
||||
}
|
||||
|
||||
for path := range errors {
|
||||
if !expectedErrors[path] {
|
||||
t.Fatalf("Unexpected error path: %s", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericValidator_ValidateRequired(t *testing.T) {
|
||||
validator := NewGenericValidator()
|
||||
|
||||
data := map[string]interface{}{
|
||||
"name": "John",
|
||||
"email": "",
|
||||
"tags": []interface{}{},
|
||||
"missing": nil,
|
||||
}
|
||||
|
||||
validator.ValidateRequired(data, "name", "[name]")
|
||||
validator.ValidateRequired(data, "email", "[email]")
|
||||
validator.ValidateRequired(data, "tags", "[tags]")
|
||||
validator.ValidateRequired(data, "missing", "[missing]")
|
||||
|
||||
if !validator.HasErrors() {
|
||||
t.Fatal("Expected validation errors")
|
||||
}
|
||||
|
||||
errors := validator.GetErrors()
|
||||
if len(errors) != 3 {
|
||||
t.Fatalf("Expected 3 errors, got %d", len(errors))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericValidator_ValidatePrice(t *testing.T) {
|
||||
validator := NewGenericValidator()
|
||||
|
||||
data := map[string]interface{}{
|
||||
"price1": 100.0,
|
||||
"price2": 0.5,
|
||||
"price3": "invalid",
|
||||
"price4": -10.0,
|
||||
}
|
||||
|
||||
validator.ValidatePrice(data, "price1", "[price1]")
|
||||
validator.ValidatePrice(data, "price2", "[price2]")
|
||||
validator.ValidatePrice(data, "price3", "[price3]")
|
||||
validator.ValidatePrice(data, "price4", "[price4]")
|
||||
|
||||
if !validator.HasErrors() {
|
||||
t.Fatal("Expected validation errors")
|
||||
}
|
||||
|
||||
errors := validator.GetErrors()
|
||||
if len(errors) != 3 {
|
||||
t.Fatalf("Expected 3 errors, got %d", len(errors))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericValidator_ValidateQuantity(t *testing.T) {
|
||||
validator := NewGenericValidator()
|
||||
|
||||
data := map[string]interface{}{
|
||||
"qty1": 10,
|
||||
"qty2": -5,
|
||||
"qty3": 3.5,
|
||||
"qty4": "invalid",
|
||||
}
|
||||
|
||||
validator.ValidateQuantity(data, "qty1", "[qty1]")
|
||||
validator.ValidateQuantity(data, "qty2", "[qty2]")
|
||||
validator.ValidateQuantity(data, "qty3", "[qty3]")
|
||||
validator.ValidateQuantity(data, "qty4", "[qty4]")
|
||||
|
||||
if !validator.HasErrors() {
|
||||
t.Fatal("Expected validation errors")
|
||||
}
|
||||
|
||||
errors := validator.GetErrors()
|
||||
if len(errors) != 3 {
|
||||
t.Fatalf("Expected 3 errors, got %d", len(errors))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericValidator_ToJSON(t *testing.T) {
|
||||
validator := NewGenericValidator()
|
||||
|
||||
validator.AddError("[name]", "این فیلد الزامی است.")
|
||||
validator.AddError("[email]", "ایمیل نامعتبر است.")
|
||||
|
||||
jsonData, err := validator.ToJSON()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got: %v", err)
|
||||
}
|
||||
|
||||
var response ErrorResponse
|
||||
if err := json.Unmarshal(jsonData, &response); err != nil {
|
||||
t.Fatalf("Expected valid JSON, got: %v", err)
|
||||
}
|
||||
|
||||
if len(response.Errors) != 2 {
|
||||
t.Fatalf("Expected 2 errors, got %d", len(response.Errors))
|
||||
}
|
||||
|
||||
if response.Errors["[name]"] != "این فیلد الزامی است." {
|
||||
t.Fatalf("Expected name error, got: %s", response.Errors["[name]"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateData(t *testing.T) {
|
||||
schema := Schema{
|
||||
"name": Rule{
|
||||
Field: "name",
|
||||
Required: true,
|
||||
},
|
||||
"age": Rule{
|
||||
Field: "age",
|
||||
Type: "int",
|
||||
},
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"name": "John",
|
||||
"age": "not a number",
|
||||
}
|
||||
|
||||
validator := ValidateData(data, schema)
|
||||
|
||||
if !validator.HasErrors() {
|
||||
t.Fatal("Expected validation errors")
|
||||
}
|
||||
|
||||
errors := validator.GetErrors()
|
||||
if len(errors) != 1 {
|
||||
t.Fatalf("Expected 1 error, got %d", len(errors))
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateJSONData(t *testing.T) {
|
||||
schema := Schema{
|
||||
"name": Rule{
|
||||
Field: "name",
|
||||
Required: true,
|
||||
},
|
||||
}
|
||||
|
||||
jsonData := []byte(`{"name": "John"}`)
|
||||
|
||||
validator, err := ValidateJSONData(jsonData, schema)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got: %v", err)
|
||||
}
|
||||
|
||||
if validator.HasErrors() {
|
||||
t.Fatal("Expected no validation errors")
|
||||
}
|
||||
|
||||
// Test invalid JSON
|
||||
invalidJSON := []byte(`{"name": "John"`)
|
||||
|
||||
_, err = ValidateJSONData(invalidJSON, schema)
|
||||
if err == nil {
|
||||
t.Fatal("Expected JSON parsing error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericValidator_ComplexNestedValidation(t *testing.T) {
|
||||
validator := NewGenericValidator()
|
||||
|
||||
// Schema for user object
|
||||
userSchema := Schema{
|
||||
"name": Rule{
|
||||
Field: "name",
|
||||
Required: true,
|
||||
},
|
||||
"age": Rule{
|
||||
Field: "age",
|
||||
Type: "int",
|
||||
},
|
||||
"email": Rule{
|
||||
Field: "email",
|
||||
Type: "string",
|
||||
},
|
||||
}
|
||||
|
||||
// Complex nested data
|
||||
data := map[string]interface{}{
|
||||
"users": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "John",
|
||||
"age": 25,
|
||||
"email": "john@example.com",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"name": "Jane",
|
||||
"age": "not a number",
|
||||
"email": "jane@example.com",
|
||||
},
|
||||
map[string]interface{}{
|
||||
// missing name
|
||||
"age": 30,
|
||||
"email": "bob@example.com",
|
||||
},
|
||||
},
|
||||
"settings": map[string]interface{}{
|
||||
"theme": "dark",
|
||||
"lang": "en",
|
||||
},
|
||||
}
|
||||
|
||||
// Validate nested users array
|
||||
validator.ValidateNested(data["users"], userSchema, "[users]")
|
||||
|
||||
if !validator.HasErrors() {
|
||||
t.Fatal("Expected validation errors")
|
||||
}
|
||||
|
||||
errors := validator.GetErrors()
|
||||
if len(errors) != 4 {
|
||||
t.Fatalf("Expected 4 errors, got %d: %v", len(errors), errors)
|
||||
}
|
||||
|
||||
// Check specific errors
|
||||
expectedErrors := map[string]bool{
|
||||
"[users][1][age]": true, // age is string instead of int
|
||||
"[users][2][name]": true, // name is missing (required)
|
||||
"[users][0][age]": true, // age is int (valid)
|
||||
"[users][0][name]": true, // name is string (valid)
|
||||
"[users][2][age]": true, // age is int (valid)
|
||||
}
|
||||
|
||||
for path := range errors {
|
||||
if !expectedErrors[path] {
|
||||
t.Fatalf("Unexpected error path: %s", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericValidator_NoErrors(t *testing.T) {
|
||||
validator := NewGenericValidator()
|
||||
|
||||
schema := Schema{
|
||||
"name": Rule{
|
||||
Field: "name",
|
||||
Required: true,
|
||||
},
|
||||
"age": Rule{
|
||||
Field: "age",
|
||||
Type: "int",
|
||||
},
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"name": "John",
|
||||
"age": 25.0, // Use float64 to match JSON unmarshaling
|
||||
}
|
||||
|
||||
validator.Validate(data, schema)
|
||||
|
||||
if validator.HasErrors() {
|
||||
errors := validator.GetErrors()
|
||||
t.Fatalf("Expected no validation errors, got: %v", errors)
|
||||
}
|
||||
|
||||
errors := validator.GetErrors()
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("Expected 0 errors, got %d", len(errors))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericValidator_EnqueueVendorStocksRequest(t *testing.T) {
|
||||
itemSchema := Schema{
|
||||
"barcode": Rule{
|
||||
Field: "barcode",
|
||||
Type: "string",
|
||||
Required: true,
|
||||
MinLength: func() *int { i := 1; return &i }(),
|
||||
},
|
||||
"stock": Rule{
|
||||
Field: "stock",
|
||||
Type: "int",
|
||||
Required: true,
|
||||
},
|
||||
}
|
||||
schema := Schema{
|
||||
"stocks": Rule{
|
||||
Field: "stocks",
|
||||
Type: "array",
|
||||
Required: true,
|
||||
MinLength: func() *int { i := 1; return &i }(),
|
||||
ArrayOf: itemSchema,
|
||||
},
|
||||
}
|
||||
|
||||
// Valid payload
|
||||
valid := map[string]interface{}{
|
||||
"vendorId": 123,
|
||||
"vendorCode": "VEND123",
|
||||
"stocks": []interface{}{
|
||||
map[string]interface{}{
|
||||
"barcode": "1234567890",
|
||||
"stock": 10.0,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"barcode": "0987654321",
|
||||
"stock": 5.0,
|
||||
},
|
||||
},
|
||||
}
|
||||
validator := NewGenericValidator()
|
||||
validator.Validate(valid, schema)
|
||||
if validator.HasErrors() {
|
||||
t.Fatalf("Expected no validation errors, got: %v", validator.GetErrors())
|
||||
}
|
||||
|
||||
// Invalid payload: missing items, empty barcode, non-int stock
|
||||
invalid := map[string]interface{}{
|
||||
"stocks": []interface{}{
|
||||
map[string]interface{}{
|
||||
"barcode": "",
|
||||
"stock": "not-an-int",
|
||||
},
|
||||
},
|
||||
}
|
||||
validator = NewGenericValidator()
|
||||
validator.Validate(invalid, schema)
|
||||
if !validator.HasErrors() {
|
||||
t.Fatal("Expected validation errors")
|
||||
}
|
||||
errors := validator.GetErrors()
|
||||
if len(errors) != 2 {
|
||||
t.Fatalf("Expected 2 errors, got %d: %v", len(errors), errors)
|
||||
}
|
||||
if _, ok := errors["[stocks][0][barcode]"]; !ok {
|
||||
t.Error("Expected error for empty barcode")
|
||||
}
|
||||
if _, ok := errors["[stocks][0][stock]"]; !ok {
|
||||
t.Error("Expected error for non-int stock")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericValidator_CustomErrorMessages(t *testing.T) {
|
||||
schema := Schema{
|
||||
"name": Rule{
|
||||
Field: "name",
|
||||
Type: "string",
|
||||
Required: true,
|
||||
RequiredMessage: "نام کاربر الزامی است.",
|
||||
TypeMessage: "نام باید از نوع متن باشد.",
|
||||
},
|
||||
"age": Rule{
|
||||
Field: "age",
|
||||
Type: "int",
|
||||
Min: func() *float64 { f := 18.0; return &f }(),
|
||||
Max: func() *float64 { f := 100.0; return &f }(),
|
||||
MinMessage: "سن باید حداقل 18 سال باشد.",
|
||||
MaxMessage: "سن نمی تواند بیشتر از 100 سال باشد.",
|
||||
TypeMessage: "سن باید عدد صحیح باشد.",
|
||||
},
|
||||
"email": Rule{
|
||||
Field: "email",
|
||||
Type: "string",
|
||||
Required: true,
|
||||
RequiredMessage: "ایمیل الزامی است.",
|
||||
PatternMessage: "فرمت ایمیل نامعتبر است.",
|
||||
},
|
||||
}
|
||||
|
||||
// Test with invalid data
|
||||
data := map[string]interface{}{
|
||||
"name": 123, // wrong type
|
||||
"age": "invalid", // wrong type
|
||||
// email is missing (not empty)
|
||||
}
|
||||
|
||||
validator := NewGenericValidator()
|
||||
validator.Validate(data, schema)
|
||||
|
||||
if !validator.HasErrors() {
|
||||
t.Fatal("Expected validation errors")
|
||||
}
|
||||
|
||||
errors := validator.GetErrors()
|
||||
|
||||
// Check custom error messages
|
||||
if errors["[name]"] != "نام باید از نوع متن باشد." {
|
||||
t.Errorf("Expected custom type error for name, got: %s", errors["[name]"])
|
||||
}
|
||||
|
||||
if errors["[age]"] != "سن باید عدد صحیح باشد." {
|
||||
t.Errorf("Expected custom type error for age, got: %s", errors["[age]"])
|
||||
}
|
||||
|
||||
if errors["[email]"] != "ایمیل الزامی است." {
|
||||
t.Errorf("Expected custom required error for email, got: %s", errors["[email]"])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user