package service import ( "errors" "testing" "github.com/freefire/jiang13-bbs/model" "github.com/glebarez/sqlite" "gorm.io/gorm" ) func newBadgeTestDB(t *testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) if err != nil { t.Fatalf("open sqlite: %v", err) } if err := db.AutoMigrate(&model.User{}, &model.Badge{}, &model.UserBadge{}); err != nil { t.Fatalf("migrate: %v", err) } return db } func TestBadgeGrantIdempotent(t *testing.T) { db := newBadgeTestDB(t) s := NewBadgeService(db) user := model.User{Username: "alice", Password: "x"} if err := db.Create(&user).Error; err != nil { t.Fatalf("seed user: %v", err) } badge, err := s.Create(" 骨灰玩家 ", "", "") if err != nil { t.Fatalf("Create: %v", err) } if badge.Name != "骨灰玩家" || badge.Color != DefaultBadgeColor { t.Fatalf("名称未规整/默认色不符: %+v", badge) } if err := s.Grant(badge.ID, user.ID, 9); err != nil { t.Fatalf("Grant: %v", err) } // 重复授予应幂等成功且不产生新行 if err := s.Grant(badge.ID, user.ID, 9); err != nil { t.Fatalf("Grant twice: %v", err) } var count int64 db.Model(&model.UserBadge{}).Where("badge_id = ?", badge.ID).Count(&count) if count != 1 { t.Fatalf("授予记录=%d want 1", count) } // 不存在的用户 if err := s.Grant(badge.ID, 999, 9); !errors.Is(err, ErrBadgeUserGone) { t.Fatalf("不存在用户应 ErrBadgeUserGone, got %v", err) } // 不存在的徽章 if err := s.Grant(999, user.ID, 9); !errors.Is(err, ErrBadgeNotFound) { t.Fatalf("不存在徽章应 ErrBadgeNotFound, got %v", err) } // ForUser 带 Badge 定义 rows, err := s.ForUser(user.ID) if err != nil || len(rows) != 1 { t.Fatalf("ForUser: %v rows=%d", err, len(rows)) } if rows[0].Badge.Name != "骨灰玩家" { t.Fatalf("Preload Badge 失败: %+v", rows[0]) } // Revoke 后再撤销 → NotFound if err := s.Revoke(badge.ID, user.ID); err != nil { t.Fatalf("Revoke: %v", err) } if err := s.Revoke(badge.ID, user.ID); !errors.Is(err, ErrBadgeNotFound) { t.Fatal("重复撤销应 ErrBadgeNotFound") } } func TestBadgeDeleteCascadeAndListCount(t *testing.T) { db := newBadgeTestDB(t) s := NewBadgeService(db) u1 := model.User{Username: "u1", Password: "x"} u2 := model.User{Username: "u2", Password: "x"} if err := db.Create(&u1).Error; err != nil { t.Fatalf("seed u1: %v", err) } if err := db.Create(&u2).Error; err != nil { t.Fatalf("seed u2: %v", err) } b1, _ := s.Create("元老", "medal", "gold") b2, _ := s.Create("萌新", "", "") if err := s.Grant(b1.ID, u1.ID, 9); err != nil { t.Fatalf("Grant b1: %v", err) } if err := s.Grant(b1.ID, u2.ID, 9); err != nil { t.Fatalf("Grant b1 u2: %v", err) } if err := s.Grant(b2.ID, u1.ID, 9); err != nil { t.Fatalf("Grant b2: %v", err) } // List 计数 list, err := s.List() if err != nil { t.Fatalf("List: %v", err) } if len(list) != 2 { t.Fatalf("徽章数=%d want 2", len(list)) } for _, b := range list { want := int64(2) if b.ID == b2.ID { want = 1 } if b.Holders != want { t.Fatalf("徽章 %d 持有数=%d want %d", b.ID, b.Holders, want) } } // 删除级联:徽章与其授予记录一起消失 if err := s.Delete(b1.ID); err != nil { t.Fatalf("Delete: %v", err) } var count int64 db.Model(&model.UserBadge{}).Where("badge_id = ?", b1.ID).Count(&count) if count != 0 { t.Fatalf("删除后授予记录=%d want 0", count) } if err := s.Delete(b1.ID); !errors.Is(err, ErrBadgeNotFound) { t.Fatal("重复删除应 ErrBadgeNotFound") } // 名称唯一冲突 if _, err := s.Create("萌新", "", ""); !errors.Is(err, ErrBadgeNameExists) { t.Fatalf("重名应 ErrBadgeNameExists, got %v", err) } } func TestBadgeValidation(t *testing.T) { s := NewBadgeService(newBadgeTestDB(t)) // 空名称 / 超长名称 / 非法颜色 if _, err := s.Create(" ", "", ""); !errors.Is(err, ErrBadgeInvalid) { t.Fatal("空名称应被拒绝") } long := make([]rune, MaxBadgeNameRunes+1) for i := range long { long[i] = '字' } if _, err := s.Create(string(long), "", ""); !errors.Is(err, ErrBadgeInvalid) { t.Fatal("超长名称应被拒绝") } if _, err := s.Create("x", "", "pink"); !errors.Is(err, ErrBadgeInvalid) { t.Fatal("色板外颜色应被拒绝") } }