Skip to content
Merged
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
280 changes: 280 additions & 0 deletions tests/update_has_one_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,15 @@ package tests

import (
"database/sql"
"errors"
"testing"

"time"

. "github.com/oracle-samples/gorm-oracle/tests/utils"

"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/utils/tests"
)

Expand Down Expand Up @@ -128,6 +130,87 @@ func TestUpdateHasOne(t *testing.T) {
CheckPetSkipUpdatedAt(t, pet4, pet)
})

t.Run("ReplaceAssociation", func(t *testing.T) {
user := *GetUser("replace-has-one", Config{})

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

acc1 := Account{AccountNumber: "first-account"}
user.Account = acc1

if err := DB.Save(&user).Error; err != nil {
t.Fatalf("errors happened when saving user with first account: %v", err)
}

acc2 := Account{AccountNumber: "second-account"}
user.Account = acc2
if err := DB.Session(&gorm.Session{FullSaveAssociations: true}).Save(&user).Error; err != nil {
t.Fatalf("errors happened when replacing association: %v", err)
}

var result User
DB.Preload("Account").First(&result, user.ID)
if result.Account.AccountNumber != "second-account" {
t.Fatalf("expected replaced account to have AccountNumber 'second-account', got %v", result.Account.AccountNumber)
}
})

t.Run("ClearHasOneAssociation", func(t *testing.T) {
user := *GetUser("nullify-has-one", Config{})

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

user.Account = Account{AccountNumber: "to-be-nullified"}
if err := DB.Save(&user).Error; err != nil {
t.Fatalf("errors happened when saving user: %v", err)
}

DB.Model(&user).Association("Account").Clear()

var result User
DB.Preload("Account").First(&result, user.ID)
if result.Account.AccountNumber != "" {
t.Fatalf("expected account to be nullified/empty, got %v", result.Account.AccountNumber)
}
})

t.Run("ClearPolymorphicAssociation", func(t *testing.T) {
pet := Pet{Name: "clear-poly"}
pet.Toy = Toy{Name: "polytoy"}
DB.Create(&pet)

DB.Model(&pet).Association("Toy").Clear()

var pet2 Pet
DB.Preload("Toy").First(&pet2, pet.ID)
if pet2.Toy.Name != "" {
t.Fatalf("expected Toy cleared, got %v", pet2.Toy.Name)
}
})

t.Run("UpdateWithoutAssociation", func(t *testing.T) {
user := *GetUser("no-assoc-update", Config{})
if err := DB.Create(&user).Error; err != nil {
t.Fatalf("errors happened when create user: %v", err)
}
newName := user.Name + "-updated"
if err := DB.Model(&user).Update("name", newName).Error; err != nil {
t.Fatalf("errors happened when updating only parent: %v", err)
}
var result User
DB.Preload("Account").First(&result, user.ID)
if result.Name != newName {
t.Fatalf("user name not updated as expected")
}
if result.Account.ID != 0 {
t.Fatalf("expected no Account associated, got ID %v", result.Account.ID)
}
})

t.Run("Restriction", func(t *testing.T) {
type CustomizeAccount struct {
gorm.Model
Expand Down Expand Up @@ -175,4 +258,201 @@ func TestUpdateHasOne(t *testing.T) {
tests.AssertEqual(t, account2.Number, number)
tests.AssertEqual(t, account2.Number2, cusUser.Account.Number2)
})

t.Run("AssociationWithoutPreload", func(t *testing.T) {
user := *GetUser("no-preload", Config{})
user.Account = Account{AccountNumber: "np-account"}
DB.Create(&user)

var result User
DB.First(&result, user.ID) // no preload
if result.Account.AccountNumber != "" {
t.Fatalf("expected Account field empty without preload, got %v", result.Account.AccountNumber)
}

var acc Account
DB.First(&acc, "\"user_id\" = ?", user.ID)
if acc.AccountNumber != "np-account" {
t.Fatalf("account not found as expected")
}
})

t.Run("SkipFullSaveAssociations", func(t *testing.T) {
user := *GetUser("skip-fsa", Config{})
user.Account = Account{AccountNumber: "skipfsa"}
DB.Create(&user)

user.Account.AccountNumber = "should-not-update"
if err := DB.Session(&gorm.Session{FullSaveAssociations: false}).Save(&user).Error; err != nil {
t.Fatalf("error saving with FSA false: %v", err)
}

var acc Account
DB.First(&acc, "\"user_id\" = ?", user.ID)
if acc.AccountNumber != "skipfsa" {
t.Fatalf("account should not have updated, got %v", acc.AccountNumber)
}
})

t.Run("HasOneZeroForeignKey", func(t *testing.T) {
now := time.Now()
user := User{Name: "zero-value-clear", Age: 18, Birthday: &now}
DB.Create(&user)

account := Account{AccountNumber: "to-clear", UserID: sql.NullInt64{Int64: int64(user.ID), Valid: true}}
DB.Create(&account)

account.UserID = sql.NullInt64{Int64: 0, Valid: false}
DB.Model(&account).Select("UserID").Updates(account)

var result User
DB.Preload("Account").First(&result, user.ID)
if result.Account.AccountNumber != "" {
t.Fatalf("expected account cleared, got %v", result.Account.AccountNumber)
}
})

t.Run("PolymorphicZeroForeignKey", func(t *testing.T) {
pet := Pet{Name: "poly-zero"}
pet.Toy = Toy{Name: "polytoy-zero"}
DB.Create(&pet)

pet.Toy.OwnerID = ""
DB.Model(&pet.Toy).Select("OwnerID").Updates(&pet.Toy)

var pet2 Pet
DB.Preload("Toy").First(&pet2, pet.ID)
if pet2.Toy.Name != "" {
t.Fatalf("expected polymorphic association cleared, got %v", pet2.Toy.Name)
}
})

t.Run("InvalidForeignKey", func(t *testing.T) {
acc := Account{AccountNumber: "badfk", UserID: sql.NullInt64{Int64: 99999999, Valid: true}}
err := DB.Create(&acc).Error
if err == nil {
t.Fatalf("expected foreign key constraint error, got nil")
}
})

t.Run("UpdateWithSelectOmit", func(t *testing.T) {
user := *GetUser("select-omit", Config{})
user.Account = Account{AccountNumber: "selomit"}
DB.Create(&user)

user.Name = "selomit-updated"
user.Account.AccountNumber = "selomit-updated"
if err := DB.Select("Name").Omit("Account").Save(&user).Error; err != nil {
t.Fatalf("error on select/omit: %v", err)
}

var acc Account
DB.First(&acc, "\"user_id\" = ?", user.ID)
if acc.AccountNumber != "selomit" {
t.Fatalf("account should not update with Omit(Account), got %v", acc.AccountNumber)
}
})

t.Run("NestedUpdate", func(t *testing.T) {
user := *GetUser("nested-update", Config{})
user.Account = Account{AccountNumber: "nested"}
DB.Create(&user)

user.Name = "nested-updated"
user.Account.AccountNumber = "nested-updated"
if err := DB.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user).Error; err != nil {
t.Fatalf("nested update failed: %v", err)
}

var result User
DB.Preload("Account").First(&result, user.ID)
if result.Name != "nested-updated" || result.Account.AccountNumber != "nested-updated" {
t.Fatalf("nested update didn't apply: %v / %v", result.Name, result.Account.AccountNumber)
}
})

t.Run("EmptyStructNoFullSave", func(t *testing.T) {
user := *GetUser("empty-nofsa", Config{})
user.Account = Account{AccountNumber: "keep"}
DB.Create(&user)

user.Account = Account{}
if err := DB.Save(&user).Error; err != nil {
t.Fatalf("save failed: %v", err)
}

var result User
DB.Preload("Account").First(&result, user.ID)
if result.Account.AccountNumber != "keep" {
t.Fatalf("account should not be cleared without FullSaveAssociations")
}
})

t.Run("DeleteParentCascade", func(t *testing.T) {
type AccountCascadeDelete struct {
gorm.Model
AccountNumber string
UserID uint
}

type UserCascadeDelete struct {
gorm.Model
Name string
Account AccountCascadeDelete `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE;"`
}

DB.Migrator().DropTable(&AccountCascadeDelete{}, &UserCascadeDelete{})
if err := DB.AutoMigrate(&UserCascadeDelete{}, &AccountCascadeDelete{}); err != nil {
t.Fatalf("failed to migrate: %v", err)
}

user := UserCascadeDelete{
Name: "delete-parent",
Account: AccountCascadeDelete{
AccountNumber: "cascade",
},
}

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

if err := DB.Unscoped().Delete(&user).Error; err != nil {
t.Fatalf("delete parent failed: %v", err)
}

var acc AccountCascadeDelete
err := DB.First(&acc, "\"user_id\" = ?", user.ID).Error
if !errors.Is(err, gorm.ErrRecordNotFound) {
t.Fatalf("expected account deleted, got %v", acc)
}
})

t.Run("OmitAllAssociations", func(t *testing.T) {
user := *GetUser("omit-assoc", Config{})
user.Account = Account{AccountNumber: "original-child"}
if err := DB.Create(&user).Error; err != nil {
t.Fatalf("failed to create user: %v", err)
}

newName := "parent-updated"
user.Name = newName
user.Account.AccountNumber = "child-updated"

if err := DB.Model(&user).Omit(clause.Associations).Updates(user).Error; err != nil {
t.Fatalf("update with omit associations failed: %v", err)
}

var result User
DB.Preload("Account").First(&result, user.ID)

if result.Name != newName {
t.Fatalf("expected parent name updated to %v, got %v", newName, result.Name)
}

if result.Account.AccountNumber != "original-child" {
t.Fatalf("expected child to remain unchanged, got %v", result.Account.AccountNumber)
}
})

}
Loading