Skip to content

Add update has one tests #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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