feat(05-02): add lagoon.Validate rule-string engine
- Translate Laravel rule strings onto validator.Var and unique:table - Custom money range via big.Rat; unique checks skip soft-deleted rows - Fill zeros nil values and Scans jsonable columns
This commit is contained in:
114
lagoon/validate_test.go
Normal file
114
lagoon/validate_test.go
Normal file
@@ -0,0 +1,114 @@
|
||||
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 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": nil}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(errs) != 0 {
|
||||
t.Fatalf("nil money = %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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user