Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions tests/associations_many2many_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,3 +461,179 @@ func TestMany2ManyDuplicateBelongsToAssociation(t *testing.T) {
tests.AssertEqual(t, nil, err)
tests.AssertEqual(t, user2, findUser2)
}

func TestMany2ManyEmptyAssociations(t *testing.T) {
user := User{Name: "TestEmptyAssociations"}

// Create user with no associations
if err := DB.Create(&user).Error; err != nil {
t.Fatalf("errors happened when create user: %v", err)
}

var languages []Language
if err := DB.Model(&user).Association("Languages").Find(&languages); err != nil {
t.Errorf("Error finding empty associations: %v", err)
}

if len(languages) != 0 {
t.Errorf("Expected 0 languages, got %d", len(languages))
}

count := DB.Model(&user).Association("Languages").Count()
if count != 0 {
t.Errorf("Expected count 0 for empty association, got %d", count)
}

if err := DB.Model(&user).Association("Languages").Clear(); err != nil {
t.Errorf("Error clearing empty association: %v", err)
}

if err := DB.Model(&user).Association("Languages").Delete(&Language{}); err != nil {
t.Errorf("Error deleting from empty association: %v", err)
}
}

func TestMany2ManyAssociationCountValidation(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test seems a dup of TestMany2ManyAssociation. Could you double check?

Copy link
Contributor Author

@saumil-oracle saumil-oracle Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they are similar, but both the tests have diff purposes:
TestMany2ManyAssociation: it has tests of all many-to-many operations (Find, Append, Replace, Delete, Clear) with basic validation.
TestMany2ManyAssociationCountValidation: I am focusing on validating the accuracy of the Count() method and ensuring it matches the actual Find() results after various operations.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the details!

user := *GetUser("count-validation", Config{Languages: 3})

if err := DB.Create(&user).Error; err != nil {
t.Fatalf("errors happened when create: %v", err)
}

// Initial count check
initialCount := DB.Model(&user).Association("Languages").Count()
if initialCount != 3 {
t.Fatalf("Expected initial count 3, got %d", initialCount)
}

newLanguages := []Language{
{Code: "count-test-1", Name: "Count Test 1"},
{Code: "count-test-2", Name: "Count Test 2"},
}
DB.Create(&newLanguages)

if err := DB.Model(&user).Association("Languages").Append(&newLanguages); err != nil {
t.Fatalf("Error appending languages: %v", err)
}

// Check count after append
countAfterAppend := DB.Model(&user).Association("Languages").Count()
if countAfterAppend != 5 {
t.Errorf("Expected count 5 after append, got %d", countAfterAppend)
}

replaceLanguage := Language{Code: "count-replace", Name: "Count Replace"}
DB.Create(&replaceLanguage)

if err := DB.Model(&user).Association("Languages").Replace(&replaceLanguage); err != nil {
t.Fatalf("Error replacing languages: %v", err)
}

// Check count after replace
countAfterReplace := DB.Model(&user).Association("Languages").Count()
if countAfterReplace != 1 {
t.Errorf("Expected count 1 after replace, got %d", countAfterReplace)
}

// Verify actual data matches count
var actualLanguages []Language
DB.Model(&user).Association("Languages").Find(&actualLanguages)
if len(actualLanguages) != int(countAfterReplace) {
t.Errorf("Count mismatch: Count() returned %d but Find() returned %d languages",
countAfterReplace, len(actualLanguages))
}
}

func TestMany2ManyConstraintViolations(t *testing.T) {
user := *GetUser("constraint-test", Config{Languages: 1})

if err := DB.Create(&user).Error; err != nil {
t.Fatalf("errors happened when create: %v", err)
}

existingLanguage := user.Languages[0]

// append the same language again
if err := DB.Model(&user).Association("Languages").Append(&existingLanguage); err != nil {
t.Logf("Appending duplicate language resulted in: %v", err)
}

// Verify count is still correct after duplicate append attempt
count := DB.Model(&user).Association("Languages").Count()
if count > 1 {
t.Errorf("Expected count 1 after duplicate append, got %d", count)
}

tempLanguage := Language{Code: "temp-invalid", Name: "Temp Invalid"}
if err := DB.Create(&tempLanguage).Error; err != nil {
t.Logf("Could not create temp language for FK test: %v", err)
return
}

// append, then delete the language record to create inconsistency
if err := DB.Model(&user).Association("Languages").Append(&tempLanguage); err != nil {
t.Logf("Could not append temp language: %v", err)
}

// Delete the language record directly
DB.Unscoped().Delete(&tempLanguage)

// access associations after deleting referenced record
var languages []Language
if err := DB.Model(&user).Association("Languages").Find(&languages); err != nil {
t.Logf("Finding associations after FK deletion resulted in: %v", err)
}

// Get count before mass operation for verification
countBeforeMass := DB.Model(&user).Association("Languages").Count()
t.Logf("Language count before mass operation: %d", countBeforeMass)

var manyLanguages []Language
for i := 0; i < 10; i++ {
manyLanguages = append(manyLanguages, Language{
Code: fmt.Sprintf("mass-test-%d", i),
Name: fmt.Sprintf("Mass Test %d", i),
})
}

// Create the languages first
if err := DB.Create(&manyLanguages).Error; err != nil {
t.Logf("Creating many languages failed: %v", err)
return
}

// append all at once
if err := DB.Model(&user).Association("Languages").Append(&manyLanguages); err != nil {
t.Logf("Mass append operation resulted in: %v", err)
}

// Verify the operation completed with proper count validation
finalCount := DB.Model(&user).Association("Languages").Count()
// Should have at least the previous count + 10 new languages
expectedMinCount := countBeforeMass + 10

t.Logf("Final language count after mass operation: %d (expected at least %d)", finalCount, expectedMinCount)

if finalCount < expectedMinCount {
t.Errorf("Expected at least %d languages after mass append, got %d", expectedMinCount, finalCount)
}

var actualLanguages []Language
if err := DB.Model(&user).Association("Languages").Find(&actualLanguages); err == nil {
actualCount := len(actualLanguages)
if actualCount != int(finalCount) {
t.Errorf("Count mismatch: Count() returned %d but Find() returned %d languages",
finalCount, actualCount)
}
}

if err := DB.Model(&user).Association("Languages").Clear(); err != nil {
t.Errorf("Error clearing associations after mass operation: %v", err)
}

// Verify clear worked
countAfterClear := DB.Model(&user).Association("Languages").Count()
if countAfterClear != 0 {
t.Errorf("Expected count 0 after clear, got %d", countAfterClear)
}
}
155 changes: 153 additions & 2 deletions tests/associations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,8 +367,8 @@ func TestAssociationEmptyQueryClause(t *testing.T) {
Organizations []Organization `gorm:"many2many:region_orgs;"`
}
type RegionOrg struct {
RegionId uint
OrganizationId uint
RegionID uint
OrganizationID uint
Empty myType
}
if err := DB.SetupJoinTable(&Region{}, "Organizations", &RegionOrg{}); err != nil {
Expand All @@ -394,3 +394,154 @@ func TestAssociationEmptyQueryClause(t *testing.T) {
tests.AssertEqual(t, len(orgs), 0)
}
}

func TestBasicBelongsToAssociation(t *testing.T) {
// Test basic BelongsTo association operations
user := GetUser("TestBelongsTo", Config{Company: true})

if err := DB.Create(&user).Error; err != nil {
t.Fatalf("Failed to create user with company: %v", err)
}

// finding the association
var foundUser User
if err := DB.Preload("Company").First(&foundUser, "\"id\" = ?", user.ID).Error; err != nil {
t.Fatalf("Failed to find user with company: %v", err)
}

if foundUser.Company.ID != user.Company.ID {
t.Fatalf("Company ID mismatch: expected %d, got %d", user.Company.ID, foundUser.Company.ID)
}

// association count
AssertAssociationCount(t, user, "Company", 1, "after creation")

// replacing association
newCompany := Company{Name: "New Test Company"}
if err := DB.Create(&newCompany).Error; err != nil {
t.Fatalf("Failed to create new company: %v", err)
}

if err := DB.Model(&user).Association("Company").Replace(&newCompany); err != nil {
t.Fatalf("Failed to replace company association: %v", err)
}

var updatedUser User
if err := DB.Preload("Company").First(&updatedUser, "\"id\" = ?", user.ID).Error; err != nil {
t.Fatalf("Failed to find updated user: %v", err)
}

if updatedUser.Company.ID != newCompany.ID {
t.Fatalf("Company was not replaced: expected %d, got %d", newCompany.ID, updatedUser.Company.ID)
}
}

func TestBasicHasManyAssociation(t *testing.T) {
// Test basic HasMany association operations
user := GetUser("TestHasMany", Config{Pets: 3})

if err := DB.Create(&user).Error; err != nil {
t.Fatalf("Failed to create user with pets: %v", err)
}

// association count
AssertAssociationCount(t, user, "Pets", 3, "after creation")

// finding pets
var pets []Pet
if err := DB.Model(&user).Association("Pets").Find(&pets); err != nil {
t.Fatalf("Failed to find pets: %v", err)
}

if len(pets) != 3 {
t.Fatalf("Expected 3 pets, got %d", len(pets))
}

// appending new pet
newPet := Pet{Name: "Additional Pet", UserID: &user.ID}
if err := DB.Model(&user).Association("Pets").Append(&newPet); err != nil {
t.Fatalf("Failed to append pet: %v", err)
}

AssertAssociationCount(t, user, "Pets", 4, "after append")

// deleting one pet from association
if err := DB.Model(&user).Association("Pets").Delete(&pets[0]); err != nil {
t.Fatalf("Failed to delete pet from association: %v", err)
}

AssertAssociationCount(t, user, "Pets", 3, "after delete")
}

func TestBasicManyToManyAssociation(t *testing.T) {
// Test basic ManyToMany association operations
user := GetUser("TestManyToMany", Config{Languages: 2})

if err := DB.Create(&user).Error; err != nil {
t.Fatalf("Failed to create user with languages: %v", err)
}

// association count
AssertAssociationCount(t, user, "Languages", 2, "after creation")

// finding languages
var languages []Language
if err := DB.Model(&user).Association("Languages").Find(&languages); err != nil {
t.Fatalf("Failed to find languages: %v", err)
}

if len(languages) != 2 {
t.Fatalf("Expected 2 languages, got %d", len(languages))
}

// appending new language
newLanguage := Language{Code: "FR", Name: "French"}
if err := DB.Create(&newLanguage).Error; err != nil {
t.Fatalf("Failed to create new language: %v", err)
}

if err := DB.Model(&user).Association("Languages").Append(&newLanguage); err != nil {
t.Fatalf("Failed to append language: %v", err)
}

AssertAssociationCount(t, user, "Languages", 3, "after append")

// replacing all languages
replaceLanguages := []Language{
{Code: "DE", Name: "German"},
{Code: "IT", Name: "Italian"},
}

for i := range replaceLanguages {
if err := DB.Create(&replaceLanguages[i]).Error; err != nil {
t.Fatalf("Failed to create replacement language: %v", err)
}
}

if err := DB.Model(&user).Association("Languages").Replace(replaceLanguages); err != nil {
t.Fatalf("Failed to replace languages: %v", err)
}

AssertAssociationCount(t, user, "Languages", 2, "after replace")

var finalLanguages []Language
if err := DB.Model(&user).Association("Languages").Find(&finalLanguages); err != nil {
t.Fatalf("Failed to find final languages: %v", err)
}

languageCodes := make(map[string]bool)
for _, lang := range finalLanguages {
languageCodes[lang.Code] = true
}

if !languageCodes["DE"] || !languageCodes["IT"] {
t.Fatal("Languages were not replaced correctly")
}

// clearing all associations
if err := DB.Model(&user).Association("Languages").Clear(); err != nil {
t.Fatalf("Failed to clear languages: %v", err)
}

AssertAssociationCount(t, user, "Languages", 0, "after clear")
}
Loading