package cabana import ( "reflect" "testing" "time" "gorm.io/gorm" ) type embeddedBase struct { ID uint `gorm:"primarykey"` CreatedAt time.Time `gorm:"column:created_at"` } type embeddedModel struct { embeddedBase Name string `gorm:"column:name"` } type gormModelRow struct { gorm.Model Name string `gorm:"column:name"` } type retaggedModel struct { Name string `gorm:"column:title"` Title string `gorm:"column:name"` Plain string } // TestModelHelpersLookThroughEmbeddedStructs pins WR-16: the reflection helpers // that resolve columns see fields promoted from an embedded struct such as // gorm.Model, so list rows keep their id and timestamps and a relation link // writes the real owner key instead of 0. func TestModelHelpersLookThroughEmbeddedStructs(t *testing.T) { row := &embeddedModel{embeddedBase: embeddedBase{ID: 7, CreatedAt: time.Unix(1700000000, 0).UTC()}, Name: "n"} if got := primaryColumn(row); got != "id" { t.Fatalf("primaryColumn = %q, want id (the embedded primarykey field)", got) } if got := pkUint(row); got != 7 { t.Fatalf("pkUint = %d, want 7", got) } cols := modelColumns(row) for _, want := range []string{"created_at", "name"} { if _, ok := cols[want]; !ok { t.Fatalf("modelColumns = %v, missing %s", cols, want) } } projected := projectRow(row, nil, []ListColumn{{Key: "name"}, {Key: "created_at"}}) if projected["id"] != uint(7) || projected["name"] != "n" || projected["created_at"] == nil { t.Fatalf("projectRow = %#v, want the embedded id and created_at", projected) } if castPK(row, 9) != uint(9) { t.Fatalf("castPK = %#v", castPK(row, 9)) } gm := &gormModelRow{Model: gorm.Model{ID: 11}, Name: "g"} if primaryColumn(gm) != "id" || pkUint(gm) != 11 { t.Fatalf("gorm.Model primary key = %q/%d, want id/11", primaryColumn(gm), pkUint(gm)) } if !fieldByColumn(reflect.ValueOf(gm).Elem(), "updated_at").IsValid() { t.Fatal("gorm.Model's UpdatedAt is not reachable by column") } } // TestFieldByColumnTagBeatsGoName pins the other half of WR-16: a field with an // explicit column tag is matched by that tag alone, never by its Go name. func TestFieldByColumnTagBeatsGoName(t *testing.T) { row := retaggedModel{Name: "in-title-column", Title: "in-name-column", Plain: "p"} v := reflect.ValueOf(row) if got := fieldByColumn(v, "name"); !got.IsValid() || got.String() != "in-name-column" { t.Fatalf("column name resolved to %v, want the field tagged column:name", got) } if got := fieldByColumn(v, "title"); !got.IsValid() || got.String() != "in-title-column" { t.Fatalf("column title resolved to %v, want the field tagged column:title", got) } if got := fieldByColumn(v, "plain"); !got.IsValid() || got.String() != "p" { t.Fatalf("an untagged field must still match its Go name case-insensitively, got %v", got) } if got := fieldByColumn(reflect.ValueOf(struct { Name string `gorm:"column:title"` }{Name: "x"}), "name"); got.IsValid() { t.Fatalf("a tagged field matched by its Go name: %v", got) } }