tests: Added tests for associations
Added tests for relationship, parsing, replace, delete
This commit is contained in:
		
							parent
							
								
									42bd4f603c
								
							
						
					
					
						commit
						7e933f20bc
					
				
							
								
								
									
										471
									
								
								association_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										471
									
								
								association_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,471 @@ | ||||
| package gorm | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"sync" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"gorm.io/gorm/clause" | ||||
| 	"gorm.io/gorm/schema" | ||||
| ) | ||||
| 
 | ||||
| type User struct { | ||||
| 	ID   uint | ||||
| 	Name string | ||||
| } | ||||
| 
 | ||||
| type TestReplaceCompany struct { | ||||
| 	ID    uint `gorm:"primaryKey"` | ||||
| 	Name  string | ||||
| 	Users []TestReplaceUser `gorm:"foreignKey:CompanyID"` | ||||
| } | ||||
| 
 | ||||
| type TestReplaceUser struct { | ||||
| 	ID        uint `gorm:"primaryKey"` | ||||
| 	Name      string | ||||
| 	CompanyID uint | ||||
| } | ||||
| 
 | ||||
| type TestM2MUser struct { | ||||
| 	ID        uint `gorm:"primaryKey"` | ||||
| 	Name      string | ||||
| 	Languages []TestM2MLanguage `gorm:"many2many:user_languages;"` | ||||
| } | ||||
| 
 | ||||
| type TestM2MLanguage struct { | ||||
| 	ID    uint `gorm:"primaryKey"` | ||||
| 	Name  string | ||||
| 	Users []TestM2MUser `gorm:"many2many:user_languages;"` | ||||
| } | ||||
| 
 | ||||
| func TestAssociation_RelationshipExists(t *testing.T) { | ||||
| 	db := &DB{ | ||||
| 		Config: &Config{ | ||||
| 			cacheStore:     &sync.Map{}, | ||||
| 			NamingStrategy: schema.NamingStrategy{}, | ||||
| 		}, | ||||
| 	} | ||||
| 	rel := &schema.Relationship{} | ||||
| 	stmt := &Statement{ | ||||
| 		DB:    db, | ||||
| 		Model: &User{}, | ||||
| 		Table: "users", | ||||
| 	} | ||||
| 	db.Statement = stmt | ||||
| 
 | ||||
| 	_ = db.Statement.Parse(db.Statement.Model) | ||||
| 	db.Statement.Schema.Relationships = schema.Relationships{ | ||||
| 		Relations: map[string]*schema.Relationship{ | ||||
| 			"User": rel, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	assoc := db.Association("User") | ||||
| 	if assoc.Error != nil { | ||||
| 		t.Errorf("expected no error, got %v", assoc.Error) | ||||
| 	} | ||||
| 	if assoc.Relationship != rel { | ||||
| 		t.Errorf("expected relationship to be set") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestAssociation_RelationshipNotExists(t *testing.T) { | ||||
| 	db := &DB{ | ||||
| 		Config: &Config{ | ||||
| 			cacheStore:     &sync.Map{}, | ||||
| 			NamingStrategy: schema.NamingStrategy{}, | ||||
| 		}, | ||||
| 	} | ||||
| 	stmt := &Statement{ | ||||
| 		DB:    db, | ||||
| 		Model: &User{}, | ||||
| 		Table: "users", | ||||
| 	} | ||||
| 	db.Statement = stmt | ||||
| 
 | ||||
| 	_ = db.Statement.Parse(db.Statement.Model) | ||||
| 	db.Statement.Schema.Relationships = schema.Relationships{ | ||||
| 		Relations: map[string]*schema.Relationship{}, | ||||
| 	} | ||||
| 
 | ||||
| 	assoc := db.Association("NotExist") | ||||
| 	if assoc.Error == nil { | ||||
| 		t.Errorf("expected error for unsupported relation") | ||||
| 	} | ||||
| 	if assoc.Relationship != nil { | ||||
| 		t.Errorf("expected relationship to be nil") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestAssociation_ParseError(t *testing.T) { | ||||
| 	db := &DB{ | ||||
| 		Config: &Config{ | ||||
| 			cacheStore:     &sync.Map{}, | ||||
| 			NamingStrategy: schema.NamingStrategy{}, | ||||
| 		}, | ||||
| 	} | ||||
| 	stmt := &Statement{ | ||||
| 		DB:    db, | ||||
| 		Model: nil, | ||||
| 		Table: "users", | ||||
| 	} | ||||
| 	db.Statement = stmt | ||||
| 
 | ||||
| 	assoc := db.Association("Any") | ||||
| 	if assoc.Error == nil { | ||||
| 		t.Errorf("expected parse error, got nil") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestAssociation_Unscoped(t *testing.T) { | ||||
| 	db := &DB{} | ||||
| 	rel := &schema.Relationship{} | ||||
| 	assoc := &Association{ | ||||
| 		DB:           db, | ||||
| 		Relationship: rel, | ||||
| 		Error:        nil, | ||||
| 		Unscope:      false, | ||||
| 	} | ||||
| 	unscoped := assoc.Unscoped() | ||||
| 	if !unscoped.Unscope { | ||||
| 		t.Errorf("expected Unscope to be true") | ||||
| 	} | ||||
| 	if unscoped.DB != db { | ||||
| 		t.Errorf("expected DB to be the same") | ||||
| 	} | ||||
| 	if unscoped.Relationship != rel { | ||||
| 		t.Errorf("expected Relationship to be the same") | ||||
| 	} | ||||
| 	if unscoped.Error != nil { | ||||
| 		t.Errorf("expected Error to be nil") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestAssociation_Find_ErrorPropagation(t *testing.T) { | ||||
| 	assoc := &Association{ | ||||
| 		Error: errAssert, | ||||
| 	} | ||||
| 	var out []User | ||||
| 	err := assoc.Find(&out) | ||||
| 	if err != errAssert { | ||||
| 		t.Errorf("expected error to propagate, got %v", err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestAssociation_Find_CallsBuildConditionAndFind(t *testing.T) { | ||||
| 	db := &DB{ | ||||
| 		Config: &Config{ | ||||
| 			cacheStore:     &sync.Map{}, | ||||
| 			NamingStrategy: schema.NamingStrategy{}, | ||||
| 			callbacks: &callbacks{ | ||||
| 				processors: map[string]*processor{ | ||||
| 					"query": {}, // Use {} instead of &processor{} for brevity if it works
 | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	stmt := &Statement{ | ||||
| 		DB:      db, | ||||
| 		Model:   &User{}, | ||||
| 		Table:   "users", | ||||
| 		Clauses: map[string]clause.Clause{}, | ||||
| 	} | ||||
| 	db.Statement = stmt | ||||
| 	_ = db.Statement.Parse(db.Statement.Model) | ||||
| 
 | ||||
| 	// Create a fully-populated dummy relationship with FieldSchema set
 | ||||
| 	fieldSchema := db.Statement.Schema | ||||
| 	rel := &schema.Relationship{ | ||||
| 		Schema: fieldSchema, | ||||
| 		Field: &schema.Field{ | ||||
| 			Name:   "User", | ||||
| 			Schema: fieldSchema, | ||||
| 		}, | ||||
| 		Type: schema.HasMany, | ||||
| 		References: []*schema.Reference{ | ||||
| 			{ | ||||
| 				PrimaryKey:   &schema.Field{Name: "ID", Schema: db.Statement.Schema}, | ||||
| 				ForeignKey:   &schema.Field{Name: "UserID", Schema: db.Statement.Schema}, | ||||
| 				PrimaryValue: "1", | ||||
| 			}, | ||||
| 		}, | ||||
| 		FieldSchema: fieldSchema, | ||||
| 	} | ||||
| 	db.Statement.Schema.Relationships = schema.Relationships{ | ||||
| 		Relations: map[string]*schema.Relationship{ | ||||
| 			"User": rel, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	assoc := db.Association("User") | ||||
| 
 | ||||
| 	var out []User | ||||
| 	err := assoc.Find(&out) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("expected no error, got %v", err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestAssociation_Append_HasOneOrBelongsTo(t *testing.T) { | ||||
| 	db := &DB{ | ||||
| 		Config: &Config{ | ||||
| 			cacheStore:     &sync.Map{}, | ||||
| 			NamingStrategy: schema.NamingStrategy{}, | ||||
| 			callbacks: &callbacks{ | ||||
| 				processors: map[string]*processor{ | ||||
| 					"query":  {}, | ||||
| 					"update": {}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	stmt := &Statement{ | ||||
| 		DB:      db, | ||||
| 		Model:   &User{}, | ||||
| 		Table:   "users", | ||||
| 		Clauses: map[string]clause.Clause{}, | ||||
| 		Context: context.Background(), | ||||
| 	} | ||||
| 	db.Statement = stmt | ||||
| 	_ = db.Statement.Parse(db.Statement.Model) | ||||
| 
 | ||||
| 	fieldSchema := db.Statement.Schema | ||||
| 	field := &schema.Field{ | ||||
| 		Name:      "User", | ||||
| 		Schema:    fieldSchema, | ||||
| 		FieldType: reflect.TypeOf(&User{}), | ||||
| 		Set: func(ctx context.Context, value reflect.Value, v interface{}) error { | ||||
| 			return nil | ||||
| 		}, | ||||
| 		ValueOf: func(ctx context.Context, value reflect.Value) (interface{}, bool) { | ||||
| 			return value.Interface(), value.IsZero() | ||||
| 		}, | ||||
| 	} | ||||
| 	fieldSchema.PrimaryFields = []*schema.Field{ | ||||
| 		{ | ||||
| 			Name:      "ID", | ||||
| 			Schema:    fieldSchema, | ||||
| 			FieldType: reflect.TypeOf(uint(0)), | ||||
| 			ValueOf: func(ctx context.Context, value reflect.Value) (interface{}, bool) { | ||||
| 				return uint(0), true | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	rel := &schema.Relationship{ | ||||
| 		Schema: fieldSchema, | ||||
| 		Field:  field, | ||||
| 		Type:   schema.HasOne, | ||||
| 		References: []*schema.Reference{ | ||||
| 			{ | ||||
| 				PrimaryKey:   fieldSchema.PrimaryFields[0], | ||||
| 				ForeignKey:   &schema.Field{Name: "UserID", Schema: fieldSchema}, | ||||
| 				PrimaryValue: "1", | ||||
| 			}, | ||||
| 		}, | ||||
| 		FieldSchema: fieldSchema, | ||||
| 	} | ||||
| 	db.Statement.Schema.Relationships = schema.Relationships{ | ||||
| 		Relations: map[string]*schema.Relationship{ | ||||
| 			"User": rel, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	assoc := db.Association("User") | ||||
| 	assoc.Relationship.Type = schema.HasOne | ||||
| 	assoc.Relationship.Field = field | ||||
| 	_ = assoc.Append(&User{}) | ||||
| 	if assoc.Error != nil { | ||||
| 		t.Errorf("expected no error, got %v", assoc.Error) | ||||
| 	} | ||||
| 
 | ||||
| 	assoc.Relationship.Type = schema.BelongsTo | ||||
| 	assoc.Relationship.Field = field | ||||
| 	_ = assoc.Append(&User{}) | ||||
| 	if assoc.Error != nil { | ||||
| 		t.Errorf("expected no error, got %v", assoc.Error) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Helper function to setup DB and Statement for association tests
 | ||||
| func setupAssociationTestDB(model interface{}, config *Config) (*DB, *Statement) { | ||||
| 	db := &DB{Config: config} | ||||
| 	stmt := &Statement{ | ||||
| 		DB:      db, | ||||
| 		Model:   model, | ||||
| 		Clauses: map[string]clause.Clause{}, | ||||
| 		Context: context.Background(), | ||||
| 	} | ||||
| 	if model != nil { | ||||
| 		stmt.ReflectValue = reflect.ValueOf(model) | ||||
| 		if err := stmt.Parse(model); err != nil { | ||||
| 			panic(fmt.Sprintf("failed to parse model in setup: %v", err)) | ||||
| 		} | ||||
| 		stmt.Table = stmt.Schema.Table | ||||
| 	} | ||||
| 	db.Statement = stmt | ||||
| 	return db, stmt | ||||
| } | ||||
| 
 | ||||
| // Helper to validate a relationship exists and has the correct properties
 | ||||
| func validateRelationship(t *testing.T, s *schema.Schema, relName string, relType schema.RelationshipType, fieldSchemaType reflect.Type, fieldName string) *schema.Relationship { | ||||
| 	t.Helper() | ||||
| 	rel, ok := s.Relationships.Relations[relName] | ||||
| 	if !ok { | ||||
| 		t.Fatalf("Relationship '%s' not found in schema", relName) | ||||
| 	} | ||||
| 	if rel.Type != relType { | ||||
| 		t.Fatalf("Relationship '%s' is not %s type, got %v", relName, relType, rel.Type) | ||||
| 	} | ||||
| 	if rel.FieldSchema == nil || rel.FieldSchema.ModelType != fieldSchemaType { | ||||
| 		t.Fatalf("Relationship '%s' FieldSchema is incorrect, expected %v, got %v", relName, fieldSchemaType, rel.FieldSchema.ModelType) | ||||
| 	} | ||||
| 	if rel.Field == nil || rel.Field.Name != fieldName { | ||||
| 		t.Fatalf("Relationship '%s' Field is incorrect, expected name '%s'", relName, fieldName) | ||||
| 	} | ||||
| 	if rel.Field.Set == nil { | ||||
| 		t.Fatalf("Relationship field '%s' has a nil Set function after parse", fieldName) | ||||
| 	} | ||||
| 	return rel | ||||
| } | ||||
| 
 | ||||
| func setPrimaryValueInReferences(t *testing.T, rel *schema.Relationship, stmt *Statement) { | ||||
| 	t.Helper() | ||||
| 	pkValue, isZero := rel.Schema.PrimaryFields[0].ValueOf(stmt.Context, stmt.ReflectValue) | ||||
| 	if isZero { | ||||
| 		t.Fatal("Primary key value is zero for the model instance") | ||||
| 	} | ||||
| 	foundRef := false | ||||
| 	for _, ref := range rel.References { | ||||
| 		if ref.PrimaryKey.Name == rel.Schema.PrimaryFields[0].Name && ref.OwnPrimaryKey { | ||||
| 			ref.PrimaryValue = fmt.Sprintf("%v", pkValue) | ||||
| 			foundRef = true | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	if !foundRef { | ||||
| 		t.Fatalf("Could not set primary value for relationship '%s'", rel.Name) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func assertReplaceHasManyResult(t *testing.T, company *TestReplaceCompany, expectedLen int, expectedNames ...string) { | ||||
| 	t.Helper() | ||||
| 	if company.Users == nil || len(company.Users) != expectedLen { | ||||
| 		t.Errorf("Expected company Users field to be updated to length %d, got: %v (len %d)", expectedLen, company.Users, len(company.Users)) | ||||
| 		return | ||||
| 	} | ||||
| 	if len(expectedNames) != expectedLen { | ||||
| 		t.Errorf("Assertion setup error: expected %d names, got %d", expectedLen, len(expectedNames)) | ||||
| 		return | ||||
| 	} | ||||
| 	for i := 0; i < expectedLen; i++ { | ||||
| 		if company.Users[i].Name != expectedNames[i] { | ||||
| 			t.Errorf("Expected company Users field content mismatch at index %d. Expected '%s', got: '%s'. Full slice: %v", i, expectedNames[i], company.Users[i].Name, company.Users) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestAssociation_Replace_HasMany_Unscoped(t *testing.T) { | ||||
| 	config := &Config{ | ||||
| 		cacheStore:     &sync.Map{}, | ||||
| 		NamingStrategy: schema.NamingStrategy{}, | ||||
| 		callbacks: &callbacks{ | ||||
| 			processors: map[string]*processor{"update": {}, "delete": {}}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	companySchema, err := schema.Parse(&TestReplaceCompany{}, config.cacheStore, config.NamingStrategy) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed to parse TestReplaceCompany schema: %v", err) | ||||
| 	} | ||||
| 	_, err = schema.Parse(&TestReplaceUser{}, config.cacheStore, config.NamingStrategy) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed to parse TestReplaceUser schema: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	company := &TestReplaceCompany{ID: 1, Name: "TestCorp"} | ||||
| 	db, stmt := setupAssociationTestDB(company, config) | ||||
| 	stmt.Schema = companySchema | ||||
| 
 | ||||
| 	rel := validateRelationship(t, companySchema, "Users", schema.HasMany, reflect.TypeOf(TestReplaceUser{}), "Users") | ||||
| 
 | ||||
| 	setPrimaryValueInReferences(t, rel, stmt) | ||||
| 
 | ||||
| 	assoc := db.Association("Users") | ||||
| 	if assoc.Error != nil { | ||||
| 		t.Fatalf("Failed to get association 'Users': %v", assoc.Error) | ||||
| 	} | ||||
| 	if assoc.Relationship != rel { | ||||
| 		t.Fatalf("Association relationship is incorrect or nil") | ||||
| 	} | ||||
| 
 | ||||
| 	assoc.Unscope = true | ||||
| 	newUsers := []*TestReplaceUser{{ID: 10, Name: "Alice"}, {ID: 11, Name: "Bob"}} | ||||
| 	err = assoc.Replace(newUsers) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Replace failed with validation/setup error: %v", err) | ||||
| 	} else { | ||||
| 		assertReplaceHasManyResult(t, company, 2, "Alice", "Bob") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestAssociation_Delete_Many2Many(t *testing.T) { | ||||
| 	config := &Config{ | ||||
| 		cacheStore:     &sync.Map{}, | ||||
| 		NamingStrategy: schema.NamingStrategy{}, | ||||
| 		callbacks:      &callbacks{processors: map[string]*processor{"delete": {}}}, // Use {}
 | ||||
| 	} | ||||
| 
 | ||||
| 	// Parse schemas
 | ||||
| 	langSchema, err := schema.Parse(&TestM2MLanguage{}, config.cacheStore, config.NamingStrategy) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed to parse TestM2MLanguage schema: %v", err) | ||||
| 	} | ||||
| 	_, err = schema.Parse(&TestM2MUser{}, config.cacheStore, config.NamingStrategy) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed to parse TestM2MUser schema: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	alice := TestM2MUser{ID: 10, Name: "Alice"} | ||||
| 	bob := TestM2MUser{ID: 11, Name: "Bob"} | ||||
| 	english := &TestM2MLanguage{ | ||||
| 		ID:    1, | ||||
| 		Name:  "English", | ||||
| 		Users: []TestM2MUser{alice, bob}, | ||||
| 	} | ||||
| 
 | ||||
| 	db, _ := setupAssociationTestDB(english, config) | ||||
| 	db.Statement.Schema = langSchema | ||||
| 
 | ||||
| 	rel := validateRelationship(t, langSchema, "Users", schema.Many2Many, reflect.TypeOf(TestM2MUser{}), "Users") | ||||
| 	if rel.JoinTable == nil { | ||||
| 		t.Fatal("Relationship 'Users' JoinTable is nil") | ||||
| 	} | ||||
| 
 | ||||
| 	assoc := db.Association("Users") | ||||
| 	if assoc.Error != nil { | ||||
| 		t.Fatalf("Failed to get association 'Users': %v", assoc.Error) | ||||
| 	} | ||||
| 	if assoc.Relationship != rel { | ||||
| 		t.Fatalf("Association relationship is incorrect or nil") | ||||
| 	} | ||||
| 
 | ||||
| 	userToDeleteAlice := TestM2MUser{ID: 10} | ||||
| 	err = assoc.Delete(&userToDeleteAlice) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Delete (Alice) failed with error: %v", err) | ||||
| 	} else { | ||||
| 		if len(english.Users) != 1 { | ||||
| 			t.Errorf("Expected english.Users length to be 1 after deleting Alice, got %d", len(english.Users)) | ||||
| 		} else if english.Users[0].ID != bob.ID || english.Users[0].Name != bob.Name { | ||||
| 			t.Errorf("Expected remaining user in english.Users to be Bob (%+v), got: %+v", bob, english.Users[0]) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| var errAssert = errors.New("assert error") | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Gaurav Dhameeja
						Gaurav Dhameeja