package reflection import ( "reflect" "testing" ) // Test models for GetModelColumnDetail type TestModelForColumnDetail struct { ID int `gorm:"column:rid_test;primaryKey;type:bigserial;not null" json:"id"` Name string `gorm:"column:name;type:varchar(255);not null" json:"name"` Email string `gorm:"column:email;type:varchar(255);unique;nullable" json:"email"` Description string `gorm:"column:description;type:text;null" json:"description"` ForeignKey int `gorm:"foreignKey:parent_id" json:"foreign_key"` } type EmbeddedBase struct { ID int `gorm:"column:rid_base;primaryKey;identity" json:"id"` CreatedAt string `gorm:"column:created_at;type:timestamp" json:"created_at"` } type ModelWithEmbeddedForDetail struct { EmbeddedBase Title string `gorm:"column:title;type:varchar(100);not null" json:"title"` Content string `gorm:"column:content;type:text" json:"content"` } // Model with nil embedded pointer type ModelWithNilEmbedded struct { ID int `gorm:"column:id;primaryKey" json:"id"` *EmbeddedBase Name string `gorm:"column:name" json:"name"` } func TestGetModelColumnDetail(t *testing.T) { t.Run("simple struct", func(t *testing.T) { model := TestModelForColumnDetail{ ID: 1, Name: "Test", Email: "test@example.com", Description: "Test description", ForeignKey: 100, } details := GetModelColumnDetail(reflect.ValueOf(model)) if len(details) != 5 { t.Errorf("Expected 5 fields, got %d", len(details)) } // Check ID field found := false for _, detail := range details { if detail.Name == "ID" { found = true if detail.SQLName != "rid_test" { t.Errorf("Expected SQLName 'rid_test', got '%s'", detail.SQLName) } // Note: primaryKey (without underscore) is not detected as primary_key // The function looks for "identity" or "primary_key" (with underscore) if detail.SQLDataType != "bigserial" { t.Errorf("Expected SQLDataType 'bigserial', got '%s'", detail.SQLDataType) } if detail.Nullable { t.Errorf("Expected Nullable false, got true") } } } if !found { t.Errorf("ID field not found in details") } }) t.Run("struct with embedded fields", func(t *testing.T) { model := ModelWithEmbeddedForDetail{ EmbeddedBase: EmbeddedBase{ ID: 1, CreatedAt: "2024-01-01", }, Title: "Test Title", Content: "Test Content", } details := GetModelColumnDetail(reflect.ValueOf(model)) // Should have 4 fields: ID, CreatedAt from embedded, Title, Content from main if len(details) != 4 { t.Errorf("Expected 4 fields, got %d", len(details)) } // Check that embedded field is included foundID := false foundCreatedAt := false for _, detail := range details { if detail.Name == "ID" { foundID = true if detail.SQLKey != "primary_key" { t.Errorf("Expected SQLKey 'primary_key' for embedded ID, got '%s'", detail.SQLKey) } } if detail.Name == "CreatedAt" { foundCreatedAt = true } } if !foundID { t.Errorf("Embedded ID field not found") } if !foundCreatedAt { t.Errorf("Embedded CreatedAt field not found") } }) t.Run("nil embedded pointer is skipped", func(t *testing.T) { model := ModelWithNilEmbedded{ ID: 1, Name: "Test", EmbeddedBase: nil, // nil embedded pointer } details := GetModelColumnDetail(reflect.ValueOf(model)) // Should have 2 fields: ID and Name (embedded is nil, so skipped) if len(details) != 2 { t.Errorf("Expected 2 fields (nil embedded skipped), got %d", len(details)) } }) t.Run("pointer to struct", func(t *testing.T) { model := &TestModelForColumnDetail{ ID: 1, Name: "Test", } details := GetModelColumnDetail(reflect.ValueOf(model)) if len(details) != 5 { t.Errorf("Expected 5 fields, got %d", len(details)) } }) t.Run("invalid value", func(t *testing.T) { var invalid reflect.Value details := GetModelColumnDetail(invalid) if len(details) != 0 { t.Errorf("Expected 0 fields for invalid value, got %d", len(details)) } }) t.Run("non-struct type", func(t *testing.T) { details := GetModelColumnDetail(reflect.ValueOf(123)) if len(details) != 0 { t.Errorf("Expected 0 fields for non-struct, got %d", len(details)) } }) t.Run("nullable and not null detection", func(t *testing.T) { model := TestModelForColumnDetail{} details := GetModelColumnDetail(reflect.ValueOf(model)) for _, detail := range details { switch detail.Name { case "ID": if detail.Nullable { t.Errorf("ID should not be nullable (has 'not null')") } case "Name": if detail.Nullable { t.Errorf("Name should not be nullable (has 'not null')") } case "Email": if !detail.Nullable { t.Errorf("Email should be nullable (has 'nullable')") } case "Description": if !detail.Nullable { t.Errorf("Description should be nullable (has 'null')") } } } }) t.Run("unique and uniqueindex detection", func(t *testing.T) { type UniqueTestModel struct { ID int `gorm:"column:id;primary_key"` Username string `gorm:"column:username;unique"` Email string `gorm:"column:email;uniqueindex"` } model := UniqueTestModel{} details := GetModelColumnDetail(reflect.ValueOf(model)) for _, detail := range details { switch detail.Name { case "ID": if detail.SQLKey != "primary_key" { t.Errorf("ID should have SQLKey 'primary_key', got '%s'", detail.SQLKey) } case "Username": if detail.SQLKey != "unique" { t.Errorf("Username should have SQLKey 'unique', got '%s'", detail.SQLKey) } case "Email": // The function checks for "unique" first, so uniqueindex is also detected as "unique" // This is expected behavior based on the code logic if detail.SQLKey != "unique" { t.Errorf("Email should have SQLKey 'unique' (uniqueindex contains 'unique'), got '%s'", detail.SQLKey) } } } }) t.Run("foreign key detection", func(t *testing.T) { // Note: The foreignkey extraction in generic_model.go has a bug where // it requires ik > 0, so foreignkey at the start won't extract the value type FKTestModel struct { ParentID int `gorm:"column:parent_id;foreignkey:rid_parent;association_foreignkey:id_atevent"` } model := FKTestModel{} details := GetModelColumnDetail(reflect.ValueOf(model)) if len(details) == 0 { t.Fatal("Expected at least 1 field") } detail := details[0] if detail.SQLKey != "foreign_key" { t.Errorf("Expected SQLKey 'foreign_key', got '%s'", detail.SQLKey) } // Due to the bug in the code (requires ik > 0), the SQLName will be extracted // when foreignkey is not at the beginning of the string if detail.SQLName != "rid_parent" { t.Errorf("Expected SQLName 'rid_parent', got '%s'", detail.SQLName) } }) } func TestFnFindKeyVal(t *testing.T) { tests := []struct { name string src string key string expected string }{ { name: "find column", src: "column:user_id;primaryKey;type:bigint", key: "column:", expected: "user_id", }, { name: "find type", src: "column:name;type:varchar(255);not null", key: "type:", expected: "varchar(255)", }, { name: "key not found", src: "primaryKey;autoIncrement", key: "column:", expected: "", }, { name: "key at end without semicolon", src: "primaryKey;column:id", key: "column:", expected: "id", }, { name: "case insensitive search", src: "Column:user_id;primaryKey", key: "column:", expected: "user_id", }, { name: "empty src", src: "", key: "column:", expected: "", }, { name: "multiple occurrences (returns first)", src: "column:first;column:second", key: "column:", expected: "first", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := fnFindKeyVal(tt.src, tt.key) if result != tt.expected { t.Errorf("fnFindKeyVal(%q, %q) = %q, want %q", tt.src, tt.key, result, tt.expected) } }) } } func TestGetModelColumnDetail_FieldValue(t *testing.T) { model := TestModelForColumnDetail{ ID: 123, Name: "TestName", Email: "test@example.com", } details := GetModelColumnDetail(reflect.ValueOf(model)) for _, detail := range details { if !detail.FieldValue.IsValid() { t.Errorf("Field %s has invalid FieldValue", detail.Name) } // Check that FieldValue matches the actual value switch detail.Name { case "ID": if detail.FieldValue.Int() != 123 { t.Errorf("Expected ID FieldValue 123, got %v", detail.FieldValue.Int()) } case "Name": if detail.FieldValue.String() != "TestName" { t.Errorf("Expected Name FieldValue 'TestName', got %v", detail.FieldValue.String()) } case "Email": if detail.FieldValue.String() != "test@example.com" { t.Errorf("Expected Email FieldValue 'test@example.com', got %v", detail.FieldValue.String()) } } } }