2020-05-31 18:55:56 +03:00
|
|
|
package tests_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
2024-06-17 06:59:06 +03:00
|
|
|
"log"
|
|
|
|
"os"
|
2020-05-31 18:55:56 +03:00
|
|
|
"reflect"
|
2020-06-05 15:24:15 +03:00
|
|
|
"strings"
|
2020-05-31 18:55:56 +03:00
|
|
|
"testing"
|
|
|
|
|
2020-06-02 04:16:07 +03:00
|
|
|
"gorm.io/gorm"
|
2020-06-05 15:24:15 +03:00
|
|
|
. "gorm.io/gorm/utils/tests"
|
2020-05-31 18:55:56 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
type Product struct {
|
|
|
|
gorm.Model
|
|
|
|
Name string
|
|
|
|
Code string
|
|
|
|
Price float64
|
|
|
|
AfterFindCallTimes int64
|
|
|
|
BeforeCreateCallTimes int64
|
|
|
|
AfterCreateCallTimes int64
|
|
|
|
BeforeUpdateCallTimes int64
|
|
|
|
AfterUpdateCallTimes int64
|
|
|
|
BeforeSaveCallTimes int64
|
|
|
|
AfterSaveCallTimes int64
|
|
|
|
BeforeDeleteCallTimes int64
|
|
|
|
AfterDeleteCallTimes int64
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Product) BeforeCreate(tx *gorm.DB) (err error) {
|
|
|
|
if s.Code == "Invalid" {
|
|
|
|
err = errors.New("invalid product")
|
|
|
|
}
|
|
|
|
s.BeforeCreateCallTimes = s.BeforeCreateCallTimes + 1
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Product) BeforeUpdate(tx *gorm.DB) (err error) {
|
|
|
|
if s.Code == "dont_update" {
|
|
|
|
err = errors.New("can't update")
|
|
|
|
}
|
|
|
|
s.BeforeUpdateCallTimes = s.BeforeUpdateCallTimes + 1
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Product) BeforeSave(tx *gorm.DB) (err error) {
|
|
|
|
if s.Code == "dont_save" {
|
|
|
|
err = errors.New("can't save")
|
|
|
|
}
|
|
|
|
s.BeforeSaveCallTimes = s.BeforeSaveCallTimes + 1
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Product) AfterFind(tx *gorm.DB) (err error) {
|
|
|
|
s.AfterFindCallTimes = s.AfterFindCallTimes + 1
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Product) AfterCreate(tx *gorm.DB) (err error) {
|
|
|
|
return tx.Model(s).UpdateColumn("AfterCreateCallTimes", s.AfterCreateCallTimes+1).Error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Product) AfterUpdate(tx *gorm.DB) (err error) {
|
|
|
|
s.AfterUpdateCallTimes = s.AfterUpdateCallTimes + 1
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Product) AfterSave(tx *gorm.DB) (err error) {
|
|
|
|
if s.Code == "after_save_error" {
|
|
|
|
err = errors.New("can't save")
|
|
|
|
}
|
|
|
|
s.AfterSaveCallTimes = s.AfterSaveCallTimes + 1
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Product) BeforeDelete(tx *gorm.DB) (err error) {
|
|
|
|
if s.Code == "dont_delete" {
|
|
|
|
err = errors.New("can't delete")
|
|
|
|
}
|
|
|
|
s.BeforeDeleteCallTimes = s.BeforeDeleteCallTimes + 1
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Product) AfterDelete(tx *gorm.DB) (err error) {
|
|
|
|
if s.Code == "after_delete_error" {
|
|
|
|
err = errors.New("can't delete")
|
|
|
|
}
|
|
|
|
s.AfterDeleteCallTimes = s.AfterDeleteCallTimes + 1
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Product) GetCallTimes() []int64 {
|
|
|
|
return []int64{s.BeforeCreateCallTimes, s.BeforeSaveCallTimes, s.BeforeUpdateCallTimes, s.AfterCreateCallTimes, s.AfterSaveCallTimes, s.AfterUpdateCallTimes, s.BeforeDeleteCallTimes, s.AfterDeleteCallTimes, s.AfterFindCallTimes}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRunCallbacks(t *testing.T) {
|
|
|
|
DB.Migrator().DropTable(&Product{})
|
|
|
|
DB.AutoMigrate(&Product{})
|
|
|
|
|
|
|
|
p := Product{Code: "unique_code", Price: 100}
|
|
|
|
DB.Save(&p)
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(p.GetCallTimes(), []int64{1, 1, 0, 1, 1, 0, 0, 0, 0}) {
|
2020-06-05 15:24:15 +03:00
|
|
|
t.Fatalf("Callbacks should be invoked successfully, %v", p.GetCallTimes())
|
2020-05-31 18:55:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
DB.Where("Code = ?", "unique_code").First(&p)
|
|
|
|
if !reflect.DeepEqual(p.GetCallTimes(), []int64{1, 1, 0, 1, 0, 0, 0, 0, 1}) {
|
|
|
|
t.Fatalf("After callbacks values are not saved, %v", p.GetCallTimes())
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Price = 200
|
|
|
|
DB.Save(&p)
|
|
|
|
if !reflect.DeepEqual(p.GetCallTimes(), []int64{1, 2, 1, 1, 1, 1, 0, 0, 1}) {
|
|
|
|
t.Fatalf("After update callbacks should be invoked successfully, %v", p.GetCallTimes())
|
|
|
|
}
|
|
|
|
|
|
|
|
var products []Product
|
|
|
|
DB.Find(&products, "code = ?", "unique_code")
|
2020-06-05 15:24:15 +03:00
|
|
|
if products[0].AfterFindCallTimes != 2 {
|
2020-05-31 18:55:56 +03:00
|
|
|
t.Fatalf("AfterFind callbacks should work with slice, called %v", products[0].AfterFindCallTimes)
|
|
|
|
}
|
|
|
|
|
|
|
|
DB.Where("Code = ?", "unique_code").First(&p)
|
|
|
|
if !reflect.DeepEqual(p.GetCallTimes(), []int64{1, 2, 1, 1, 0, 0, 0, 0, 2}) {
|
|
|
|
t.Fatalf("After update callbacks values are not saved, %v", p.GetCallTimes())
|
|
|
|
}
|
|
|
|
|
|
|
|
DB.Delete(&p)
|
|
|
|
if !reflect.DeepEqual(p.GetCallTimes(), []int64{1, 2, 1, 1, 0, 0, 1, 1, 2}) {
|
|
|
|
t.Fatalf("After delete callbacks should be invoked successfully, %v", p.GetCallTimes())
|
|
|
|
}
|
|
|
|
|
|
|
|
if DB.Where("Code = ?", "unique_code").First(&p).Error == nil {
|
|
|
|
t.Fatalf("Can't find a deleted record")
|
|
|
|
}
|
2021-02-07 07:44:59 +03:00
|
|
|
|
|
|
|
beforeCallTimes := p.AfterFindCallTimes
|
|
|
|
if DB.Where("Code = ?", "unique_code").Find(&p).Error != nil {
|
|
|
|
t.Fatalf("Find don't raise error when record not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
if p.AfterFindCallTimes != beforeCallTimes {
|
|
|
|
t.Fatalf("AfterFind should not be called")
|
|
|
|
}
|
2020-05-31 18:55:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestCallbacksWithErrors(t *testing.T) {
|
|
|
|
DB.Migrator().DropTable(&Product{})
|
|
|
|
DB.AutoMigrate(&Product{})
|
|
|
|
|
|
|
|
p := Product{Code: "Invalid", Price: 100}
|
|
|
|
if DB.Save(&p).Error == nil {
|
|
|
|
t.Fatalf("An error from before create callbacks happened when create with invalid value")
|
|
|
|
}
|
|
|
|
|
|
|
|
if DB.Where("code = ?", "Invalid").First(&Product{}).Error == nil {
|
|
|
|
t.Fatalf("Should not save record that have errors")
|
|
|
|
}
|
|
|
|
|
|
|
|
if DB.Save(&Product{Code: "dont_save", Price: 100}).Error == nil {
|
|
|
|
t.Fatalf("An error from after create callbacks happened when create with invalid value")
|
|
|
|
}
|
|
|
|
|
|
|
|
p2 := Product{Code: "update_callback", Price: 100}
|
|
|
|
DB.Save(&p2)
|
|
|
|
|
|
|
|
p2.Code = "dont_update"
|
|
|
|
if DB.Save(&p2).Error == nil {
|
|
|
|
t.Fatalf("An error from before update callbacks happened when update with invalid value")
|
|
|
|
}
|
|
|
|
|
|
|
|
if DB.Where("code = ?", "update_callback").First(&Product{}).Error != nil {
|
|
|
|
t.Fatalf("Record Should not be updated due to errors happened in before update callback")
|
|
|
|
}
|
|
|
|
|
|
|
|
if DB.Where("code = ?", "dont_update").First(&Product{}).Error == nil {
|
|
|
|
t.Fatalf("Record Should not be updated due to errors happened in before update callback")
|
|
|
|
}
|
|
|
|
|
|
|
|
p2.Code = "dont_save"
|
|
|
|
if DB.Save(&p2).Error == nil {
|
|
|
|
t.Fatalf("An error from before save callbacks happened when update with invalid value")
|
|
|
|
}
|
|
|
|
|
|
|
|
p3 := Product{Code: "dont_delete", Price: 100}
|
|
|
|
DB.Save(&p3)
|
|
|
|
if DB.Delete(&p3).Error == nil {
|
|
|
|
t.Fatalf("An error from before delete callbacks happened when delete")
|
|
|
|
}
|
|
|
|
|
|
|
|
if DB.Where("Code = ?", "dont_delete").First(&p3).Error != nil {
|
|
|
|
t.Fatalf("An error from before delete callbacks happened")
|
|
|
|
}
|
|
|
|
|
|
|
|
p4 := Product{Code: "after_save_error", Price: 100}
|
|
|
|
DB.Save(&p4)
|
|
|
|
if err := DB.First(&Product{}, "code = ?", "after_save_error").Error; err == nil {
|
|
|
|
t.Fatalf("Record should be reverted if get an error in after save callback")
|
|
|
|
}
|
|
|
|
|
|
|
|
p5 := Product{Code: "after_delete_error", Price: 100}
|
|
|
|
DB.Save(&p5)
|
|
|
|
if err := DB.First(&Product{}, "code = ?", "after_delete_error").Error; err != nil {
|
|
|
|
t.Fatalf("Record should be found")
|
|
|
|
}
|
|
|
|
|
|
|
|
DB.Delete(&p5)
|
|
|
|
if err := DB.First(&Product{}, "code = ?", "after_delete_error").Error; err != nil {
|
|
|
|
t.Fatalf("Record shouldn't be deleted because of an error happened in after delete callback")
|
|
|
|
}
|
|
|
|
}
|
2020-06-05 15:24:15 +03:00
|
|
|
|
|
|
|
type Product2 struct {
|
|
|
|
gorm.Model
|
|
|
|
Name string
|
|
|
|
Code string
|
|
|
|
Price int64
|
|
|
|
Owner string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s Product2) BeforeCreate(tx *gorm.DB) (err error) {
|
|
|
|
if !strings.HasSuffix(s.Name, "_clone") {
|
|
|
|
newProduft := s
|
|
|
|
newProduft.Price *= 2
|
|
|
|
newProduft.Name += "_clone"
|
|
|
|
err = tx.Create(&newProduft).Error
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.Name == "Invalid" {
|
|
|
|
return errors.New("invalid")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Product2) BeforeUpdate(tx *gorm.DB) (err error) {
|
|
|
|
tx.Statement.Where("owner != ?", "admin")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestUseDBInHooks(t *testing.T) {
|
|
|
|
DB.Migrator().DropTable(&Product2{})
|
|
|
|
DB.AutoMigrate(&Product2{})
|
|
|
|
|
|
|
|
product := Product2{Name: "Invalid", Price: 100}
|
|
|
|
|
|
|
|
if err := DB.Create(&product).Error; err == nil {
|
|
|
|
t.Fatalf("should returns error %v when creating product, but got nil", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
product2 := Product2{Name: "Nice", Price: 100}
|
|
|
|
|
|
|
|
if err := DB.Create(&product2).Error; err != nil {
|
|
|
|
t.Fatalf("Failed to create product, got error: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var result Product2
|
|
|
|
if err := DB.First(&result, "name = ?", "Nice").Error; err != nil {
|
|
|
|
t.Fatalf("Failed to query product, got error: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var resultClone Product2
|
|
|
|
if err := DB.First(&resultClone, "name = ?", "Nice_clone").Error; err != nil {
|
|
|
|
t.Fatalf("Failed to find cloned product, got error: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
result.Price *= 2
|
|
|
|
result.Name += "_clone"
|
|
|
|
AssertObjEqual(t, result, resultClone, "Price", "Name")
|
|
|
|
|
|
|
|
DB.Model(&result).Update("Price", 500)
|
|
|
|
var result2 Product2
|
|
|
|
DB.First(&result2, "name = ?", "Nice")
|
|
|
|
|
|
|
|
if result2.Price != 500 {
|
|
|
|
t.Errorf("Failed to update product's price, expects: %v, got %v", 500, result2.Price)
|
|
|
|
}
|
|
|
|
|
|
|
|
product3 := Product2{Name: "Nice2", Price: 600, Owner: "admin"}
|
|
|
|
if err := DB.Create(&product3).Error; err != nil {
|
|
|
|
t.Fatalf("Failed to create product, got error: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var result3 Product2
|
|
|
|
if err := DB.First(&result3, "name = ?", "Nice2").Error; err != nil {
|
|
|
|
t.Fatalf("Failed to query product, got error: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
DB.Model(&result3).Update("Price", 800)
|
|
|
|
var result4 Product2
|
|
|
|
DB.First(&result4, "name = ?", "Nice2")
|
|
|
|
|
|
|
|
if result4.Price != 600 {
|
|
|
|
t.Errorf("Admin product's price should not be changed, expects: %v, got %v", 600, result4.Price)
|
|
|
|
}
|
|
|
|
}
|
2020-06-30 11:53:54 +03:00
|
|
|
|
|
|
|
type Product3 struct {
|
|
|
|
gorm.Model
|
|
|
|
Name string
|
|
|
|
Code string
|
|
|
|
Price int64
|
|
|
|
Owner string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s Product3) BeforeCreate(tx *gorm.DB) (err error) {
|
|
|
|
tx.Statement.SetColumn("Price", s.Price+100)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s Product3) BeforeUpdate(tx *gorm.DB) (err error) {
|
|
|
|
if tx.Statement.Changed() {
|
|
|
|
tx.Statement.SetColumn("Price", s.Price+10)
|
|
|
|
}
|
|
|
|
|
|
|
|
if tx.Statement.Changed("Code") {
|
|
|
|
s.Price += 20
|
|
|
|
tx.Statement.SetColumn("Price", s.Price+30)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSetColumn(t *testing.T) {
|
|
|
|
DB.Migrator().DropTable(&Product3{})
|
|
|
|
DB.AutoMigrate(&Product3{})
|
|
|
|
|
|
|
|
product := Product3{Name: "Product", Price: 0}
|
|
|
|
DB.Create(&product)
|
|
|
|
|
|
|
|
if product.Price != 100 {
|
|
|
|
t.Errorf("invalid price after create, got %+v", product)
|
|
|
|
}
|
|
|
|
|
|
|
|
DB.Model(&product).Select("code", "price").Updates(map[string]interface{}{"code": "L1212"})
|
|
|
|
|
|
|
|
if product.Price != 150 || product.Code != "L1212" {
|
|
|
|
t.Errorf("invalid data after update, got %+v", product)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Code not changed, price should not change
|
|
|
|
DB.Model(&product).Updates(map[string]interface{}{"Name": "Product New"})
|
|
|
|
|
|
|
|
if product.Name != "Product New" || product.Price != 160 || product.Code != "L1212" {
|
|
|
|
t.Errorf("invalid data after update, got %+v", product)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Code changed, but not selected, price should not change
|
|
|
|
DB.Model(&product).Select("Name", "Price").Updates(map[string]interface{}{"Name": "Product New2", "code": "L1213"})
|
|
|
|
|
|
|
|
if product.Name != "Product New2" || product.Price != 170 || product.Code != "L1212" {
|
|
|
|
t.Errorf("invalid data after update, got %+v", product)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Code changed, price should changed
|
|
|
|
DB.Model(&product).Select("Name", "Code", "Price").Updates(map[string]interface{}{"Name": "Product New3", "code": "L1213"})
|
|
|
|
|
|
|
|
if product.Name != "Product New3" || product.Price != 220 || product.Code != "L1213" {
|
|
|
|
t.Errorf("invalid data after update, got %+v", product)
|
|
|
|
}
|
|
|
|
|
|
|
|
var result Product3
|
|
|
|
DB.First(&result, product.ID)
|
|
|
|
|
|
|
|
AssertEqual(t, result, product)
|
|
|
|
|
2020-10-27 13:14:36 +03:00
|
|
|
// Select to change Code, but nothing updated, price should not change
|
|
|
|
DB.Model(&product).Select("code").Updates(Product3{Name: "L1214", Code: "L1213"})
|
2020-06-30 11:53:54 +03:00
|
|
|
|
2020-10-27 13:14:36 +03:00
|
|
|
if product.Price != 220 || product.Code != "L1213" || product.Name != "Product New3" {
|
|
|
|
t.Errorf("invalid data after update, got %+v", product)
|
|
|
|
}
|
|
|
|
|
|
|
|
DB.Model(&product).Updates(Product3{Code: "L1214"})
|
|
|
|
if product.Price != 270 || product.Code != "L1214" {
|
|
|
|
t.Errorf("invalid data after update, got %+v", product)
|
|
|
|
}
|
|
|
|
|
2022-04-24 04:08:52 +03:00
|
|
|
// Code changed, price should changed
|
|
|
|
DB.Model(&product).Select("Name", "Code", "Price").Updates(Product3{Name: "Product New4", Code: ""})
|
|
|
|
if product.Name != "Product New4" || product.Price != 320 || product.Code != "" {
|
|
|
|
t.Errorf("invalid data after update, got %+v", product)
|
|
|
|
}
|
|
|
|
|
2020-10-27 13:14:36 +03:00
|
|
|
DB.Model(&product).UpdateColumns(Product3{Code: "L1215"})
|
2022-04-24 04:08:52 +03:00
|
|
|
if product.Price != 320 || product.Code != "L1215" {
|
2020-06-30 11:53:54 +03:00
|
|
|
t.Errorf("invalid data after update, got %+v", product)
|
|
|
|
}
|
|
|
|
|
2020-11-17 10:19:58 +03:00
|
|
|
DB.Model(&product).Session(&gorm.Session{SkipHooks: true}).Updates(Product3{Code: "L1216"})
|
2022-04-24 04:08:52 +03:00
|
|
|
if product.Price != 320 || product.Code != "L1216" {
|
2020-11-17 10:19:58 +03:00
|
|
|
t.Errorf("invalid data after update, got %+v", product)
|
|
|
|
}
|
|
|
|
|
2020-06-30 11:53:54 +03:00
|
|
|
var result2 Product3
|
|
|
|
DB.First(&result2, product.ID)
|
|
|
|
|
|
|
|
AssertEqual(t, result2, product)
|
2020-11-17 10:41:17 +03:00
|
|
|
|
|
|
|
product2 := Product3{Name: "Product", Price: 0}
|
|
|
|
DB.Session(&gorm.Session{SkipHooks: true}).Create(&product2)
|
|
|
|
|
|
|
|
if product2.Price != 0 {
|
|
|
|
t.Errorf("invalid price after create without hooks, got %+v", product2)
|
|
|
|
}
|
2020-06-30 11:53:54 +03:00
|
|
|
}
|
2020-06-30 17:47:21 +03:00
|
|
|
|
|
|
|
func TestHooksForSlice(t *testing.T) {
|
2020-07-01 16:28:19 +03:00
|
|
|
DB.Migrator().DropTable(&Product3{})
|
|
|
|
DB.AutoMigrate(&Product3{})
|
|
|
|
|
2020-06-30 17:47:21 +03:00
|
|
|
products := []*Product3{
|
|
|
|
{Name: "Product-1", Price: 100},
|
|
|
|
{Name: "Product-2", Price: 200},
|
|
|
|
{Name: "Product-3", Price: 300},
|
|
|
|
}
|
|
|
|
|
|
|
|
DB.Create(&products)
|
|
|
|
|
|
|
|
for idx, value := range []int64{200, 300, 400} {
|
|
|
|
if products[idx].Price != value {
|
|
|
|
t.Errorf("invalid price for product #%v, expects: %v, got %v", idx, value, products[idx].Price)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DB.Model(&products).Update("Name", "product-name")
|
|
|
|
|
|
|
|
// will set all product's price to last product's price + 10
|
|
|
|
for idx, value := range []int64{410, 410, 410} {
|
|
|
|
if products[idx].Price != value {
|
|
|
|
t.Errorf("invalid price for product #%v, expects: %v, got %v", idx, value, products[idx].Price)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
products2 := []Product3{
|
|
|
|
{Name: "Product-1", Price: 100},
|
|
|
|
{Name: "Product-2", Price: 200},
|
|
|
|
{Name: "Product-3", Price: 300},
|
|
|
|
}
|
|
|
|
|
|
|
|
DB.Create(&products2)
|
|
|
|
|
|
|
|
for idx, value := range []int64{200, 300, 400} {
|
|
|
|
if products2[idx].Price != value {
|
|
|
|
t.Errorf("invalid price for product #%v, expects: %v, got %v", idx, value, products2[idx].Price)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DB.Model(&products2).Update("Name", "product-name")
|
|
|
|
|
|
|
|
// will set all product's price to last product's price + 10
|
|
|
|
for idx, value := range []int64{410, 410, 410} {
|
|
|
|
if products2[idx].Price != value {
|
|
|
|
t.Errorf("invalid price for product #%v, expects: %v, got %v", idx, value, products2[idx].Price)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-07-01 16:28:19 +03:00
|
|
|
|
|
|
|
type Product4 struct {
|
|
|
|
gorm.Model
|
|
|
|
Name string
|
|
|
|
Code string
|
|
|
|
Price int64
|
|
|
|
Owner string
|
|
|
|
Item ProductItem
|
|
|
|
}
|
|
|
|
|
|
|
|
type ProductItem struct {
|
|
|
|
gorm.Model
|
2022-05-04 13:57:53 +03:00
|
|
|
Code string
|
|
|
|
Product4ID uint
|
|
|
|
AfterFindCallTimes int
|
2020-07-01 16:28:19 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (pi ProductItem) BeforeCreate(*gorm.DB) error {
|
|
|
|
if pi.Code == "invalid" {
|
|
|
|
return errors.New("invalid item")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-05-04 13:57:53 +03:00
|
|
|
func (pi *ProductItem) AfterFind(*gorm.DB) error {
|
|
|
|
pi.AfterFindCallTimes = pi.AfterFindCallTimes + 1
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-07-01 16:28:19 +03:00
|
|
|
func TestFailedToSaveAssociationShouldRollback(t *testing.T) {
|
|
|
|
DB.Migrator().DropTable(&Product4{}, &ProductItem{})
|
|
|
|
DB.AutoMigrate(&Product4{}, &ProductItem{})
|
|
|
|
|
|
|
|
product := Product4{Name: "Product-1", Price: 100, Item: ProductItem{Code: "invalid"}}
|
|
|
|
if err := DB.Create(&product).Error; err == nil {
|
|
|
|
t.Errorf("should got failed to save, but error is nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
if DB.First(&Product4{}, "name = ?", product.Name).Error == nil {
|
|
|
|
t.Errorf("should got RecordNotFound, but got nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
product = Product4{Name: "Product-2", Price: 100, Item: ProductItem{Code: "valid"}}
|
|
|
|
if err := DB.Create(&product).Error; err != nil {
|
|
|
|
t.Errorf("should create product, but got error %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := DB.First(&Product4{}, "name = ?", product.Name).Error; err != nil {
|
|
|
|
t.Errorf("should find product, but got error %v", err)
|
|
|
|
}
|
2022-05-04 13:57:53 +03:00
|
|
|
|
|
|
|
var productWithItem Product4
|
|
|
|
if err := DB.Session(&gorm.Session{SkipHooks: true}).Preload("Item").First(&productWithItem, "name = ?", product.Name).Error; err != nil {
|
|
|
|
t.Errorf("should find product, but got error %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if productWithItem.Item.AfterFindCallTimes != 0 {
|
|
|
|
t.Fatalf("AfterFind should not be called times:%d", productWithItem.Item.AfterFindCallTimes)
|
|
|
|
}
|
2020-07-01 16:28:19 +03:00
|
|
|
}
|
2023-02-18 04:20:29 +03:00
|
|
|
|
|
|
|
type Product5 struct {
|
|
|
|
gorm.Model
|
|
|
|
Name string
|
|
|
|
}
|
|
|
|
|
|
|
|
var beforeUpdateCall int
|
|
|
|
|
|
|
|
func (p *Product5) BeforeUpdate(*gorm.DB) error {
|
|
|
|
beforeUpdateCall = beforeUpdateCall + 1
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestUpdateCallbacks(t *testing.T) {
|
|
|
|
DB.Migrator().DropTable(&Product5{})
|
|
|
|
DB.AutoMigrate(&Product5{})
|
|
|
|
|
|
|
|
p := Product5{Name: "unique_code"}
|
|
|
|
DB.Model(&Product5{}).Create(&p)
|
|
|
|
|
|
|
|
err := DB.Model(&Product5{}).Where("id", p.ID).Update("name", "update_name_1").Error
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("should update success, but got err %v", err)
|
|
|
|
}
|
|
|
|
if beforeUpdateCall != 1 {
|
|
|
|
t.Fatalf("before update should be called")
|
|
|
|
}
|
|
|
|
|
|
|
|
err = DB.Model(Product5{}).Where("id", p.ID).Update("name", "update_name_2").Error
|
|
|
|
if !errors.Is(err, gorm.ErrInvalidValue) {
|
|
|
|
t.Fatalf("should got RecordNotFound, but got %v", err)
|
|
|
|
}
|
|
|
|
if beforeUpdateCall != 1 {
|
|
|
|
t.Fatalf("before update should not be called")
|
|
|
|
}
|
|
|
|
|
|
|
|
err = DB.Model([1]*Product5{&p}).Update("name", "update_name_3").Error
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("should update success, but got err %v", err)
|
|
|
|
}
|
|
|
|
if beforeUpdateCall != 2 {
|
|
|
|
t.Fatalf("before update should be called")
|
|
|
|
}
|
|
|
|
|
|
|
|
err = DB.Model([1]Product5{p}).Update("name", "update_name_4").Error
|
|
|
|
if !errors.Is(err, gorm.ErrInvalidValue) {
|
|
|
|
t.Fatalf("should got RecordNotFound, but got %v", err)
|
|
|
|
}
|
|
|
|
if beforeUpdateCall != 2 {
|
|
|
|
t.Fatalf("before update should not be called")
|
|
|
|
}
|
|
|
|
}
|
2024-06-17 06:59:06 +03:00
|
|
|
|
|
|
|
type Product6 struct {
|
|
|
|
gorm.Model
|
|
|
|
Name string
|
|
|
|
Item *ProductItem2
|
|
|
|
}
|
|
|
|
|
|
|
|
type ProductItem2 struct {
|
|
|
|
gorm.Model
|
|
|
|
Product6ID uint
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Product6) BeforeDelete(tx *gorm.DB) error {
|
|
|
|
if err := tx.Delete(&p.Item).Error; err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPropagateUnscoped(t *testing.T) {
|
|
|
|
_DB, err := OpenTestConnection(&gorm.Config{
|
|
|
|
PropagateUnscoped: true,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("failed to connect database, got error %v", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
_DB.Migrator().DropTable(&Product6{}, &ProductItem2{})
|
|
|
|
_DB.AutoMigrate(&Product6{}, &ProductItem2{})
|
|
|
|
|
|
|
|
p := Product6{
|
|
|
|
Name: "unique_code",
|
|
|
|
Item: &ProductItem2{},
|
|
|
|
}
|
|
|
|
_DB.Model(&Product6{}).Create(&p)
|
|
|
|
|
|
|
|
if err := _DB.Unscoped().Delete(&p).Error; err != nil {
|
|
|
|
t.Fatalf("unscoped did not propagate")
|
|
|
|
}
|
|
|
|
}
|