package gormvrtx import ( "testing" "gorm.io/gorm" ) // SvcM 软删除模型 — 报告 #1/#2/#3 的回归: 零 DeletedAt 必须落成 NULL, // 软删除作用域 (deleted_at IS NULL) 下 UPDATE/Delete/First 必须生效。 type SvcM struct { gorm.Model Title string `gorm:"size:32"` Port int } func (SvcM) TableName() string { return "services_m" } func TestSoftDeleteScope(t *testing.T) { db := openTest(t) if err := db.AutoMigrate(&SvcM{}); err != nil { t.Fatal(err) } if err := db.Create(&SvcM{Title: "原始", Port: 11}).Error; err != nil { t.Fatal(err) } // #1: 单字段更新 (GORM 自动附加 deleted_at IS NULL 作用域) if err := db.Model(&SvcM{}).Where("id = ?", 1).Update("title", "字面量A").Error; err != nil { t.Fatal(err) } var m SvcM if err := db.First(&m, 1).Error; err != nil { t.Fatal(err) } if m.Title != "字面量A" || m.Port != 11 { t.Fatalf("#1 更新未生效: %+v", m) } // #2: UPDATE 带 IS NULL 条件 (GORM 写路径的软删除作用域) res := db.Model(&SvcM{}).Where("id = ? AND deleted_at IS NULL", 1).Update("port", 33) if res.Error != nil || res.RowsAffected != 1 { t.Fatalf("#2 IS NULL 作用域失效: rows=%d err=%v", res.RowsAffected, res.Error) } // #3: 结构体 Updates (含零值 DeletedAt) — deleted_at 必须保持 NULL m.Title = "结构B" res = db.Model(&SvcM{}).Where("id = ?", 1).Updates(&m) if res.Error != nil || res.RowsAffected != 1 { t.Fatalf("#3 Updates: rows=%d err=%v", res.RowsAffected, res.Error) } var m2 SvcM if err := db.Unscoped().First(&m2, 1).Error; err != nil { t.Fatal(err) } if m2.Title != "结构B" || m2.DeletedAt.Valid { t.Fatalf("#3 零 DeletedAt 未归一成 NULL: %+v", m2) } // 软删除 + 作用域隐藏 if err := db.Delete(&SvcM{}, 1).Error; err != nil { t.Fatal(err) } if err := db.First(&m, 1).Error; err == nil { t.Fatalf("软删除后 First 仍命中") } if err := db.Unscoped().First(&m2, 1).Error; err != nil { t.Fatalf("Unscoped 读不到: %v", err) } if !m2.DeletedAt.Valid { t.Fatalf("软删除标记未写入") } }