package model import ( "testing" "github.com/QuantumNous/new-api/common" "github.com/glebarez/sqlite" "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/gorm" ) func setupWechatTopUpDB(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 common.QuotaPerUnit = 500 * 1000.0 require.NoError(t, db.AutoMigrate(&User{}, &TopUp{}, &Log{})) t.Cleanup(func() { DB = origDB LOG_DB = origLogDB sqlDB.Close() }) return db } func TestRechargeWechat_Success(t *testing.T) { db := setupWechatTopUpDB(t) // 创建测试用户 user := User{ Id: 200, Username: "wechat_test_user", Password: "hashed_password", Quota: 100000, AffCode: "aff_wx_200", } require.NoError(t, db.Create(&user).Error) // 创建 pending 状态的充值订单 expectedQuota := int(decimal.NewFromFloat(10.0).Mul(decimal.NewFromFloat(common.QuotaPerUnit)).IntPart()) topUp := &TopUp{ UserId: 200, Amount: 10, Money: 10.0, // 10 元 TradeNo: "wx_test_trade_001", PaymentMethod: "wechat_pay", CreateTime: common.GetTimestamp(), Status: common.TopUpStatusPending, } require.NoError(t, db.Create(topUp).Error) // 执行充值 err := RechargeWechat("wx_test_trade_001") require.NoError(t, err) // 验证用户额度已增加 var updatedUser User require.NoError(t, db.First(&updatedUser, 200).Error) assert.Equal(t, 100000+expectedQuota, updatedUser.Quota) // 验证订单状态已更新 var updatedTopUp TopUp require.NoError(t, db.Where("trade_no = ?", "wx_test_trade_001").First(&updatedTopUp).Error) assert.Equal(t, common.TopUpStatusSuccess, updatedTopUp.Status) assert.NotZero(t, updatedTopUp.CompleteTime) } func TestRechargeWechat_OrderNotFound(t *testing.T) { setupWechatTopUpDB(t) err := RechargeWechat("non_existent_trade_no") assert.Error(t, err) assert.Contains(t, err.Error(), "充值订单不存在") } func TestRechargeWechat_EmptyTradeNo(t *testing.T) { setupWechatTopUpDB(t) err := RechargeWechat("") assert.Error(t, err) assert.Contains(t, err.Error(), "未提供支付单号") } func TestRechargeWechat_AlreadyCompleted(t *testing.T) { db := setupWechatTopUpDB(t) // 创建测试用户 user := User{ Id: 201, Username: "wechat_test_user_2", Password: "hashed_password", Quota: 100000, AffCode: "aff_wx_201", } require.NoError(t, db.Create(&user).Error) // 创建已完成的充值订单 topUp := &TopUp{ UserId: 201, Amount: 10, Money: 10.0, TradeNo: "wx_test_trade_completed", PaymentMethod: "wechat_pay", CreateTime: common.GetTimestamp(), CompleteTime: common.GetTimestamp(), Status: common.TopUpStatusSuccess, } require.NoError(t, db.Create(topUp).Error) // 再次充值应幂等返回成功 err := RechargeWechat("wx_test_trade_completed") require.NoError(t, err) // 验证额度未变化 var updatedUser User require.NoError(t, db.First(&updatedUser, 201).Error) assert.Equal(t, 100000, updatedUser.Quota) // 额度不变 } func TestRechargeWechat_InvalidStatus(t *testing.T) { db := setupWechatTopUpDB(t) // 创建测试用户 user := User{ Id: 202, Username: "wechat_test_user_3", Password: "hashed_password", Quota: 100000, AffCode: "aff_wx_202", } require.NoError(t, db.Create(&user).Error) // 创建 expired 状态的订单 topUp := &TopUp{ UserId: 202, Amount: 10, Money: 10.0, TradeNo: "wx_test_trade_expired", PaymentMethod: "wechat_pay", CreateTime: common.GetTimestamp(), Status: common.TopUpStatusExpired, } require.NoError(t, db.Create(topUp).Error) // 应返回状态错误 err := RechargeWechat("wx_test_trade_expired") assert.Error(t, err) assert.Contains(t, err.Error(), "充值订单状态错误") // 验证额度未变化 var updatedUser User require.NoError(t, db.First(&updatedUser, 202).Error) assert.Equal(t, 100000, updatedUser.Quota) } func TestRechargeWechat_ConcurrentSafety(t *testing.T) { db := setupWechatTopUpDB(t) // 创建测试用户 user := User{ Id: 203, Username: "wechat_concurrent_user", Password: "hashed_password", Quota: 100000, AffCode: "aff_wx_203", } require.NoError(t, db.Create(&user).Error) expectedQuota := int(decimal.NewFromFloat(10.0).Mul(decimal.NewFromFloat(common.QuotaPerUnit)).IntPart()) // 创建 pending 订单 topUp := &TopUp{ UserId: 203, Amount: 10, Money: 10.0, TradeNo: "wx_concurrent_trade", PaymentMethod: "wechat_pay", CreateTime: common.GetTimestamp(), Status: common.TopUpStatusPending, } require.NoError(t, db.Create(topUp).Error) // 并发调用 RechargeWechat,只有第一个应该成功充值 done := make(chan error, 2) go func() { done <- RechargeWechat("wx_concurrent_trade") }() go func() { done <- RechargeWechat("wx_concurrent_trade") }() err1 := <-done err2 := <-done // 至少一个成功,不能两个都失败 require.True(t, err1 == nil || err2 == nil, "至少一个并发请求应成功") // 验证额度只增加了一次(因为 SQLite 单连接,实际上是序列化的) var updatedUser User require.NoError(t, db.First(&updatedUser, 203).Error) assert.Equal(t, 100000+expectedQuota, updatedUser.Quota, "额度应只增加一次") } func TestRechargeWechat_QuotaCalculation(t *testing.T) { db := setupWechatTopUpDB(t) testCases := []struct { name string money float64 initialQuota int expectedAdd int userId int }{ {"整数金额", 7.0, 0, int(decimal.NewFromFloat(7.0).Mul(decimal.NewFromFloat(common.QuotaPerUnit)).IntPart()), 310}, {"小数金额", 7.5, 0, int(decimal.NewFromFloat(7.5).Mul(decimal.NewFromFloat(common.QuotaPerUnit)).IntPart()), 311}, {"最小金额", 0.01, 0, int(decimal.NewFromFloat(0.01).Mul(decimal.NewFromFloat(common.QuotaPerUnit)).IntPart()), 312}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { userId := tc.userId tradeNo := "wx_quota_test_" + tc.name // 创建用户 user := User{ Id: userId, Username: "quota_test_" + tc.name, Password: "hashed", Quota: tc.initialQuota, AffCode: "aff_" + tc.name, } require.NoError(t, db.Create(&user).Error) // 创建订单 topUp := &TopUp{ UserId: userId, Amount: 1, Money: tc.money, TradeNo: tradeNo, PaymentMethod: "wechat_pay", CreateTime: common.GetTimestamp(), Status: common.TopUpStatusPending, } require.NoError(t, db.Create(topUp).Error) err := RechargeWechat(tradeNo) require.NoError(t, err) var updatedUser User require.NoError(t, db.First(&updatedUser, userId).Error) assert.Equal(t, tc.initialQuota+tc.expectedAdd, updatedUser.Quota, tc.name) }) } }