package lagoon import ( "strings" "testing" ) func TestValidateBetweenYear(t *testing.T) { rules := map[string]string{"year": "nullable|integer|between:1889,2100"} errs, err := Validate(t.Context(), nil, nil, rules, map[string]any{"year": 1700}, nil) if err != nil { t.Fatal(err) } if len(errs["year"]) == 0 { t.Fatal("year=1700 must fail") } errs, err = Validate(t.Context(), nil, nil, rules, map[string]any{"year": nil}, nil) if err != nil { t.Fatal(err) } if len(errs) != 0 { t.Fatalf("nil year = %v", errs) } errs, err = Validate(t.Context(), nil, nil, rules, map[string]any{"year": 1991}, nil) if err != nil { t.Fatal(err) } if len(errs) != 0 { t.Fatalf("1991 = %v", errs) } } func TestValidateIntegerPointerAndJSONNumber(t *testing.T) { rules := map[string]string{"year": "nullable|integer|between:1889,2100"} year := 1991 var nilYear *int pass := map[string]any{ "*int": &year, "nil *int": nilYear, "float64": float64(1991), "digit string": "1991", } for name, val := range pass { errs, err := Validate(t.Context(), nil, nil, rules, map[string]any{"year": val}, nil) if err != nil { t.Fatalf("%s: %v", name, err) } if len(errs) != 0 { t.Fatalf("%s must pass, got %v", name, errs) } } zero := 0 early := 1700 fail := map[string]any{ "fractional float": 1991.5, "out of range *int": &early, "bool": true, "non-digit string": "19x1", "int 0": 0, "float64 0": float64(0), "*int 0": &zero, "string 0": "0", } for name, val := range fail { errs, err := Validate(t.Context(), nil, nil, rules, map[string]any{"year": val}, nil) if err != nil { t.Fatalf("%s: %v", name, err) } if len(errs["year"]) == 0 { t.Fatalf("%s must fail", name) } } } func TestValidateOneofQuotedSpace(t *testing.T) { rules := map[string]string{"format": `nullable|in:LP,2LP,CD,2CD,MC,Box,EP 7"`} errs, err := Validate(t.Context(), nil, nil, rules, map[string]any{"format": `EP 7"`}, nil) if err != nil { t.Fatal(err) } if len(errs) != 0 { t.Fatalf("EP 7\" rejected: %v", errs) } errs, err = Validate(t.Context(), nil, nil, rules, map[string]any{"format": "tape"}, nil) if err != nil { t.Fatal(err) } if len(errs["format"]) == 0 { t.Fatal("invalid format must fail") } } func TestValidateMoneyRange(t *testing.T) { rules := map[string]string{"market_price_stored": "nullable|numeric|min:0|max:999999.9999"} errs, err := Validate(t.Context(), nil, nil, rules, map[string]any{"market_price_stored": "1000000.0000"}, nil) if err != nil { t.Fatal(err) } if len(errs["market_price_stored"]) == 0 { t.Fatal("1000000.0000 must fail max") } errs, err = Validate(t.Context(), nil, nil, rules, map[string]any{"market_price_stored": "25.0000"}, nil) if err != nil { t.Fatal(err) } if len(errs) != 0 { t.Fatalf("25.0000 = %v", errs) } errs, err = Validate(t.Context(), nil, nil, rules, map[string]any{"market_price_stored": 0}, nil) if err != nil { t.Fatal(err) } if len(errs) != 0 { t.Fatalf("numeric 0 must pass min:0, got %v", errs) } errs, err = Validate(t.Context(), nil, nil, rules, map[string]any{"market_price_stored": nil}, nil) if err != nil { t.Fatal(err) } if len(errs) != 0 { t.Fatalf("nil money = %v", errs) } } func TestValidateBooleanNoop(t *testing.T) { rules := map[string]string{"search_use_typesense": "boolean"} for _, v := range []any{true, false} { errs, err := Validate(t.Context(), nil, nil, rules, map[string]any{"search_use_typesense": v}, nil) if err != nil { t.Fatal(err) } if len(errs) != 0 { t.Fatalf("boolean %v = %v", v, errs) } } } func TestValidateUnrecognizedRule(t *testing.T) { _, err := Validate(t.Context(), nil, nil, map[string]string{"name": "required|nope"}, map[string]any{"name": "x"}, nil) if err == nil || !strings.Contains(err.Error(), "nope") { t.Fatalf("want unrecognized nope, got %v", err) } } func TestValidateUniqueRespectsDeletedAt(t *testing.T) { sqlDB, _ := dedicatedDB(t, "lagoon_validate_unique") gdb, err := Use(t.Context(), sqlDB) if err != nil { t.Fatal(err) } if err := gdb.Exec(`CREATE TABLE lagoon_unique_rows ( id SERIAL PRIMARY KEY, slug TEXT NOT NULL, deleted_at TIMESTAMPTZ )`).Error; err != nil { t.Fatal(err) } if err := gdb.Exec(`INSERT INTO lagoon_unique_rows (slug, deleted_at) VALUES ('live', NULL), ('gone', NOW())`).Error; err != nil { t.Fatal(err) } rules := map[string]string{"slug": "unique:lagoon_unique_rows"} errs, err := Validate(t.Context(), gdb, nil, rules, map[string]any{"slug": "live"}, nil) if err != nil { t.Fatal(err) } if len(errs["slug"]) == 0 { t.Fatal("live slug must fail unique") } errs, err = Validate(t.Context(), gdb, nil, rules, map[string]any{"slug": "gone"}, nil) if err != nil { t.Fatal(err) } if len(errs) != 0 { t.Fatalf("soft-deleted slug should pass unique: %v", errs) } }