Files
summercms/modules/cabana/model_fields_test.go

125 lines
4.3 KiB
Go

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)
}
}
type listTag struct {
ID uint `gorm:"primarykey"`
Name *string `gorm:"column:name"`
}
type listTagged struct {
ID uint `gorm:"primarykey"`
Tags []listTag `gorm:"many2many:tagged_tags"`
Pointers []*listTag `gorm:"many2many:tagged_pointer_tags"`
}
// TestProjectRowJoinsToManyRelation pins a to-many relation column (relation:
// tags, select: name): the list shows every related record's selected value
// joined with ", " like Winter, and leaves the key unset when there are none.
func TestProjectRowJoinsToManyRelation(t *testing.T) {
news, guides := "News", "Guides"
row := &listTagged{
ID: 3,
Tags: []listTag{{ID: 1, Name: &news}, {ID: 2, Name: &guides}, {ID: 4}},
Pointers: []*listTag{nil, {ID: 2, Name: &guides}},
}
cols := []ListColumn{
{Key: "tags", Relation: "tags", Select: "name"},
{Key: "pointers", Relation: "pointers", Select: "name"},
}
projected := projectRow(row, nil, cols)
if projected["tags"] != "News, Guides" {
t.Fatalf("tags = %#v, want %q", projected["tags"], "News, Guides")
}
if projected["pointers"] != "Guides" {
t.Fatalf("pointers = %#v, want %q", projected["pointers"], "Guides")
}
empty := projectRow(&listTagged{ID: 5}, nil, cols)
if _, ok := empty["tags"]; ok {
t.Fatalf("empty relation projected %#v, want the key unset", empty)
}
}