softdelete_test.go 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. package gormvrtx
  2. import (
  3. "testing"
  4. "gorm.io/gorm"
  5. )
  6. // SvcM 软删除模型 — 报告 #1/#2/#3 的回归: 零 DeletedAt 必须落成 NULL,
  7. // 软删除作用域 (deleted_at IS NULL) 下 UPDATE/Delete/First 必须生效。
  8. type SvcM struct {
  9. gorm.Model
  10. Title string `gorm:"size:32"`
  11. Port int
  12. }
  13. func (SvcM) TableName() string { return "services_m" }
  14. func TestSoftDeleteScope(t *testing.T) {
  15. db := openTest(t)
  16. if err := db.AutoMigrate(&SvcM{}); err != nil {
  17. t.Fatal(err)
  18. }
  19. if err := db.Create(&SvcM{Title: "原始", Port: 11}).Error; err != nil {
  20. t.Fatal(err)
  21. }
  22. // #1: 单字段更新 (GORM 自动附加 deleted_at IS NULL 作用域)
  23. if err := db.Model(&SvcM{}).Where("id = ?", 1).Update("title", "字面量A").Error; err != nil {
  24. t.Fatal(err)
  25. }
  26. var m SvcM
  27. if err := db.First(&m, 1).Error; err != nil {
  28. t.Fatal(err)
  29. }
  30. if m.Title != "字面量A" || m.Port != 11 {
  31. t.Fatalf("#1 更新未生效: %+v", m)
  32. }
  33. // #2: UPDATE 带 IS NULL 条件 (GORM 写路径的软删除作用域)
  34. res := db.Model(&SvcM{}).Where("id = ? AND deleted_at IS NULL", 1).Update("port", 33)
  35. if res.Error != nil || res.RowsAffected != 1 {
  36. t.Fatalf("#2 IS NULL 作用域失效: rows=%d err=%v", res.RowsAffected, res.Error)
  37. }
  38. // #3: 结构体 Updates (含零值 DeletedAt) — deleted_at 必须保持 NULL
  39. m.Title = "结构B"
  40. res = db.Model(&SvcM{}).Where("id = ?", 1).Updates(&m)
  41. if res.Error != nil || res.RowsAffected != 1 {
  42. t.Fatalf("#3 Updates: rows=%d err=%v", res.RowsAffected, res.Error)
  43. }
  44. var m2 SvcM
  45. if err := db.Unscoped().First(&m2, 1).Error; err != nil {
  46. t.Fatal(err)
  47. }
  48. if m2.Title != "结构B" || m2.DeletedAt.Valid {
  49. t.Fatalf("#3 零 DeletedAt 未归一成 NULL: %+v", m2)
  50. }
  51. // 软删除 + 作用域隐藏
  52. if err := db.Delete(&SvcM{}, 1).Error; err != nil {
  53. t.Fatal(err)
  54. }
  55. if err := db.First(&m, 1).Error; err == nil {
  56. t.Fatalf("软删除后 First 仍命中")
  57. }
  58. if err := db.Unscoped().First(&m2, 1).Error; err != nil {
  59. t.Fatalf("Unscoped 读不到: %v", err)
  60. }
  61. if !m2.DeletedAt.Valid {
  62. t.Fatalf("软删除标记未写入")
  63. }
  64. }