package model import ( "testing" "github.com/QuantumNous/new-api/common" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/gorm" ) func setupRedemptionDB(t *testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) sqlDB, _ := db.DB() sqlDB.SetMaxOpenConns(1) origDB := DB origLogDB := LOG_DB DB = db LOG_DB = db common.UsingSQLite = true common.RedisEnabled = false require.NoError(t, db.AutoMigrate(&User{}, &Redemption{}, &Log{})) t.Cleanup(func() { DB = origDB LOG_DB = origLogDB sqlDB.Close() }) return db } func TestRedeem_Success(t *testing.T) { db := setupRedemptionDB(t) // 创建测试用户 user := User{ Id: 100, Username: "redeem_test", Password: "hashed_password", Quota: 100000, } require.NoError(t, db.Create(&user).Error) // 创建兑换码 redemption := Redemption{ Id: 1, UserId: 1, Key: "test-key-123", Status: common.RedemptionCodeStatusEnabled, Quota: 50000, CreatedTime: common.GetTimestamp(), ExpiredTime: 0, // 永不过期 } require.NoError(t, db.Create(&redemption).Error) // 设置回调 callbackCalled := false var receivedUserId int var receivedQuota int SetQuotaUpdateCallback(func(userId int, quota int) { callbackCalled = true receivedUserId = userId receivedQuota = quota }) defer SetQuotaUpdateCallback(nil) // 执行兑换 quota, err := Redeem("test-key-123", 100) require.NoError(t, err) assert.Equal(t, 50000, quota) // 验证回调被触发 assert.True(t, callbackCalled) assert.Equal(t, 100, receivedUserId) assert.Equal(t, 150000, receivedQuota) // 原始 100000 + 兑换 50000 // 验证用户额度已更新 var updatedUser User require.NoError(t, db.First(&updatedUser, 100).Error) assert.Equal(t, 150000, updatedUser.Quota) // 验证兑换码状态已更新 var updatedRedemption Redemption require.NoError(t, db.First(&updatedRedemption, 1).Error) assert.Equal(t, common.RedemptionCodeStatusUsed, updatedRedemption.Status) assert.Equal(t, 100, updatedRedemption.UsedUserId) } func TestRedeem_InvalidKey(t *testing.T) { setupRedemptionDB(t) // 测试无效的兑换码 quota, err := Redeem("invalid-key", 100) assert.Error(t, err) assert.Equal(t, 0, quota) } func TestRedeem_AlreadyUsed(t *testing.T) { db := setupRedemptionDB(t) // 创建测试用户 user := User{ Id: 100, Username: "redeem_used_test", Password: "hashed_password", Quota: 100000, } require.NoError(t, db.Create(&user).Error) // 创建已使用的兑换码 redemption := Redemption{ Id: 1, UserId: 1, Key: "used-key", Status: common.RedemptionCodeStatusUsed, Quota: 50000, CreatedTime: common.GetTimestamp(), RedeemedTime: common.GetTimestamp(), UsedUserId: 99, } require.NoError(t, db.Create(&redemption).Error) // 尝试兑换已使用的兑换码 quota, err := Redeem("used-key", 100) assert.Error(t, err) assert.Equal(t, 0, quota) // 错误会被包装为 ErrRedeemFailed,但底层错误消息会记录在日志中 assert.Equal(t, ErrRedeemFailed, err) } func TestRedeem_Expired(t *testing.T) { db := setupRedemptionDB(t) // 创建测试用户 user := User{ Id: 100, Username: "redeem_expired_test", Password: "hashed_password", Quota: 100000, } require.NoError(t, db.Create(&user).Error) // 创建已过期的兑换码 redemption := Redemption{ Id: 1, UserId: 1, Key: "expired-key", Status: common.RedemptionCodeStatusEnabled, Quota: 50000, CreatedTime: common.GetTimestamp() - 86400, ExpiredTime: common.GetTimestamp() - 3600, // 已过期 } require.NoError(t, db.Create(&redemption).Error) // 尝试兑换已过期的兑换码 quota, err := Redeem("expired-key", 100) assert.Error(t, err) assert.Equal(t, 0, quota) // 错误会被包装为 ErrRedeemFailed assert.Equal(t, ErrRedeemFailed, err) } func TestRedeem_CallbackPanic(t *testing.T) { db := setupRedemptionDB(t) // 创建测试用户 user := User{ Id: 100, Username: "redeem_panic_test", Password: "hashed_password", Quota: 100000, } require.NoError(t, db.Create(&user).Error) // 创建兑换码 redemption := Redemption{ Id: 1, UserId: 1, Key: "panic-key", Status: common.RedemptionCodeStatusEnabled, Quota: 50000, CreatedTime: common.GetTimestamp(), } require.NoError(t, db.Create(&redemption).Error) // 设置一个会 panic 的回调 SetQuotaUpdateCallback(func(userId int, quota int) { panic("callback panic in redeem") }) defer SetQuotaUpdateCallback(nil) // 执行兑换(回调 panic 不应影响兑换成功) quota, err := Redeem("panic-key", 100) require.NoError(t, err) assert.Equal(t, 50000, quota) // 验证用户额度已更新 var updatedUser User require.NoError(t, db.First(&updatedUser, 100).Error) assert.Equal(t, 150000, updatedUser.Quota) } func TestRedeem_SyncedUser(t *testing.T) { db := setupRedemptionDB(t) // 创建同步用户(从 Master 节点同步过来的) user := User{ Id: 100, Username: "synced_redeem_test", Password: "hashed_password", Quota: 100000, Source: common.UserSourceSynced, RemoteUserId: 1000, SyncedQuota: 100000, } require.NoError(t, db.Create(&user).Error) // 创建兑换码 redemption := Redemption{ Id: 1, UserId: 1, Key: "synced-redeem-key", Status: common.RedemptionCodeStatusEnabled, Quota: 50000, CreatedTime: common.GetTimestamp(), } require.NoError(t, db.Create(&redemption).Error) // 设置回调 callbackCalled := false SetQuotaUpdateCallback(func(userId int, quota int) { callbackCalled = true }) defer SetQuotaUpdateCallback(nil) // 执行兑换 quota, err := Redeem("synced-redeem-key", 100) require.NoError(t, err) assert.Equal(t, 50000, quota) // 验证回调被触发(Master 节点需要同步到 Slave) assert.True(t, callbackCalled) // 验证用户额度已更新 var updatedUser User require.NoError(t, db.First(&updatedUser, 100).Error) assert.Equal(t, 150000, updatedUser.Quota) }