fix(10.1): CR-01 answer a value that does not fit its column with a 422
lagoon.Fill now returns a *lagoon.FillTypeError naming the key when a requested value cannot be stored in its column (a fraction, exponent or overflow for an integer field, or a value of the wrong type). The admin save path maps it to a validation_failed 422 on that field instead of a 500 CapabilityError; genuine capability failures keep the 500.
This commit is contained in:
@@ -469,3 +469,86 @@ func crudBody(t *testing.T, raw string) map[string]any {
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
type crudYearRow struct {
|
||||
ID uint `gorm:"column:id;primaryKey"`
|
||||
Name string `gorm:"column:name"`
|
||||
Year *int `gorm:"column:year"`
|
||||
}
|
||||
|
||||
func (crudYearRow) TableName() string { return "cabana_crud_year_rows" }
|
||||
func (crudYearRow) Fillable() []string { return []string{"name", "year"} }
|
||||
func (crudYearRow) Rules() map[string]string { return map[string]string{"name": "required"} }
|
||||
|
||||
// TestCRUDFillTypeIsValidation covers CR-01: a value lagoon.Fill cannot store
|
||||
// in its column (a fraction, an exponent or an overflow for an integer field,
|
||||
// or a value of the wrong type) is the admin's input, so create and update
|
||||
// answer 422 with a message on that field rather than a 500 capability error.
|
||||
func TestCRUDFillTypeIsValidation(t *testing.T) {
|
||||
_, db := newListService(t)
|
||||
if err := db.Migrator().DropTable(&crudYearRow{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.AutoMigrate(&crudYearRow{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fsys := crudFS()
|
||||
fsys["models/record/fields.yaml"] = &fstest.MapFile{Data: []byte(`fields:
|
||||
name:
|
||||
label: Name
|
||||
type: text
|
||||
required: true
|
||||
year:
|
||||
label: Year
|
||||
type: number
|
||||
`)}
|
||||
reg, err := compileRegistry([]controllerRef{{
|
||||
plugin: formPlugin{fsys: fsys},
|
||||
ctl: crudController{rec: func() any { return &crudYearRow{} }},
|
||||
}})
|
||||
if err != nil {
|
||||
t.Fatalf("registry: %v", err)
|
||||
}
|
||||
cc, ok := reg.Get("acme.demo.records")
|
||||
if !ok {
|
||||
t.Fatal("compiled controller missing")
|
||||
}
|
||||
svc := CRUDService{DB: db}
|
||||
ctx := context.Background()
|
||||
count := func() int64 {
|
||||
t.Helper()
|
||||
var n int64
|
||||
if err := db.Model(&crudYearRow{}).Count(&n).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
for _, raw := range []string{`{"name":"Ada","year":1977.5}`, `{"name":"Ada","year":1e21}`, `{"name":"Ada","year":99999999999999999999}`, `{"name":"Ada","year":"1977"}`} {
|
||||
_, err := svc.Create(ctx, cc, RecordInput{Body: crudBody(t, raw)})
|
||||
assertValidation(t, err, "year", "The year field has an invalid value.")
|
||||
if n := count(); n != 0 {
|
||||
t.Fatalf("%s: rows=%d, an invalid year persisted", raw, n)
|
||||
}
|
||||
}
|
||||
_, err = svc.Create(ctx, cc, RecordInput{Body: crudBody(t, `{"name":true}`)})
|
||||
assertValidation(t, err, "name", "The name field has an invalid value.")
|
||||
|
||||
rec, err := svc.Create(ctx, cc, RecordInput{Body: crudBody(t, `{"name":"Ada","year":1977}`)})
|
||||
if err != nil || rec["year"] == nil {
|
||||
t.Fatalf("create=%#v err=%v, want year 1977", rec, err)
|
||||
}
|
||||
var stored crudYearRow
|
||||
if err := db.Where("name = ?", "Ada").Take(&stored).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = svc.Update(ctx, cc, stored.ID, RecordInput{Body: crudBody(t, `{"year":1977.5}`)})
|
||||
assertValidation(t, err, "year", "The year field has an invalid value.")
|
||||
var after crudYearRow
|
||||
if err := db.Take(&after, stored.ID).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if after.Year == nil || *after.Year != 1977 {
|
||||
t.Fatalf("year after rejected update = %v, want 1977", after.Year)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user