package gift import ( "context" "errors" "testing" bizgift "kra/internal/biz/gift" "kra/internal/data/ledger" "github.com/glebarez/sqlite" "gorm.io/gorm" ) type testProvider struct{ db *gorm.DB } func (p testProvider) DB() *gorm.DB { return p.db } func openGiftTestDB(t *testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{}) if err != nil { t.Fatal(err) } if err = db.AutoMigrate(&giftPO{}, &ledger.WalletPO{}, &ledger.GiftSendPO{}, &ledger.SettingPO{}); err != nil { t.Fatal(err) } for _, statement := range []string{ `CREATE TABLE sys_users (id integer primary key, username text, nick_name text)`, `CREATE TABLE live_rooms (id integer primary key, name text, anchor_user_id integer, enabled numeric, deleted_at datetime)`, } { if err = db.Exec(statement).Error; err != nil { t.Fatal(err) } } return db } func TestGiftRepoProtectsLastEnabledGift(t *testing.T) { db := openGiftTestDB(t) repo := NewRepo(testProvider{db: db}) item, err := repo.Save(context.Background(), &bizgift.Gift{Name: "Rose", Icon: "rose", Price: 1, Tiers: []int{1}, Enabled: true}) if err != nil { t.Fatal(err) } if err = repo.SetEnabled(context.Background(), item.ID, false); !errors.Is(err, bizgift.ErrLastEnabled) { t.Fatalf("disable last gift error = %v", err) } if err = repo.Delete(context.Background(), item.ID); !errors.Is(err, bizgift.ErrLastEnabled) { t.Fatalf("delete last gift error = %v", err) } } func TestGiftRepoSendUsesTierAndDebitsAtomically(t *testing.T) { db := openGiftTestDB(t) repo := NewRepo(testProvider{db: db}) if err := db.Exec(`INSERT INTO sys_users(id,username,nick_name) VALUES (1,'viewer','Viewer'),(2,'anchor','Anchor')`).Error; err != nil { t.Fatal(err) } if err := db.Exec(`INSERT INTO live_rooms(id,name,anchor_user_id,enabled) VALUES (7,'Room',2,1)`).Error; err != nil { t.Fatal(err) } if err := db.Create(&ledger.WalletPO{UserID: 1, DiamondBalance: 100}).Error; err != nil { t.Fatal(err) } item, err := repo.Save(context.Background(), &bizgift.Gift{Name: "Star", Icon: "star", Price: 5, Tiers: []int{1, 10}, Enabled: true}) if err != nil { t.Fatal(err) } if _, err = repo.Send(context.Background(), 1, 7, item.ID, 2); err != nil { t.Fatalf("custom quantity should be accepted: %v", err) } var before ledger.WalletPO if err = db.Where("user_id = ?", 1).First(&before).Error; err != nil || before.DiamondBalance != 90 { t.Fatalf("wallet did not debit custom quantity: wallet=%+v err=%v", before, err) } if _, err = repo.Send(context.Background(), 1, 7, item.ID, 10); err != nil { t.Fatal(err) } var after ledger.WalletPO var sends int64 db.Where("user_id = ?", 1).First(&after) db.Model(&ledger.GiftSendPO{}).Count(&sends) if after.DiamondBalance != 40 || after.TotalSpentDiamonds != 60 || sends != 2 { t.Fatalf("atomic send result wallet=%+v sends=%d", after, sends) } } func TestSeedDefaultCatalogAddsMissingGiftsWithoutOverwriting(t *testing.T) { db := openGiftTestDB(t) custom := giftPO{Name: "玫瑰", Icon: "custom", Price: 99, Description: "admin configured", Tiers: "[1]", Sort: 88, Enabled: false} if err := db.Create(&custom).Error; err != nil { t.Fatal(err) } if err := db.Model(&giftPO{}).Where("id = ?", custom.ID).Update("enabled", false).Error; err != nil { t.Fatal(err) } if err := seedDefaultCatalog(db); err != nil { t.Fatal(err) } if err := seedDefaultCatalog(db); err != nil { t.Fatal(err) } var count int64 if err := db.Model(&giftPO{}).Count(&count).Error; err != nil { t.Fatal(err) } if count != 24 { t.Fatalf("gift count = %d, want 24", count) } var rose giftPO if err := db.Where("name = ?", "玫瑰").First(&rose).Error; err != nil { t.Fatal(err) } if rose.Icon != "custom" || rose.Price != 99 || rose.Sort != 88 || rose.Enabled { t.Fatalf("existing gift was overwritten: %+v", rose) } }