643 lines
14 KiB
Go
643 lines
14 KiB
Go
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]"])
|
|
}
|
|
}
|