|
- 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)
- }
-
- func TestRedemptionUpdateRemarkPersistsRemark(t *testing.T) {
- db := setupRedemptionDB(t)
-
- redemption := Redemption{
- Id: 1,
- UserId: 1,
- Key: "remark-update-key",
- Status: common.RedemptionCodeStatusEnabled,
- Name: "starter",
- Remark: "initial-note",
- Quota: 100,
- CreatedTime: common.GetTimestamp(),
- }
- require.NoError(t, db.Create(&redemption).Error)
-
- redemption.Remark = "vip-updated"
- require.NoError(t, redemption.Update())
-
- var updated Redemption
- require.NoError(t, db.First(&updated, redemption.Id).Error)
- assert.Equal(t, "vip-updated", updated.Remark)
- }
-
- func TestSearchRedemptionsByRemarkRemark(t *testing.T) {
- db := setupRedemptionDB(t)
-
- require.NoError(t, db.Create(&Redemption{
- Id: 1,
- UserId: 1,
- Key: "remark-search-key",
- Status: common.RedemptionCodeStatusEnabled,
- Name: "starter-pack",
- Remark: "vip benefit",
- Quota: 100,
- CreatedTime: common.GetTimestamp(),
- }).Error)
- require.NoError(t, db.Create(&Redemption{
- Id: 2,
- UserId: 1,
- Key: "remark-search-other",
- Status: common.RedemptionCodeStatusEnabled,
- Name: "basic-pack",
- Remark: "standard benefit",
- Quota: 100,
- CreatedTime: common.GetTimestamp(),
- }).Error)
-
- results, total, err := SearchRedemptions("vip", 0, 10)
- require.NoError(t, err)
- require.Equal(t, int64(1), total)
- require.Len(t, results, 1)
- assert.Equal(t, 1, results[0].Id)
- assert.Equal(t, "vip benefit", results[0].Remark)
- }
|