Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 

264 řádky
7.0 KiB

  1. package model
  2. import (
  3. "testing"
  4. "github.com/QuantumNous/new-api/common"
  5. "github.com/glebarez/sqlite"
  6. "github.com/shopspring/decimal"
  7. "github.com/stretchr/testify/assert"
  8. "github.com/stretchr/testify/require"
  9. "gorm.io/gorm"
  10. )
  11. func setupWechatTopUpDB(t *testing.T) *gorm.DB {
  12. t.Helper()
  13. db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
  14. require.NoError(t, err)
  15. sqlDB, _ := db.DB()
  16. sqlDB.SetMaxOpenConns(1)
  17. origDB := DB
  18. origLogDB := LOG_DB
  19. DB = db
  20. LOG_DB = db
  21. common.UsingSQLite = true
  22. common.RedisEnabled = false
  23. common.QuotaPerUnit = 500 * 1000.0
  24. require.NoError(t, db.AutoMigrate(&User{}, &TopUp{}, &Log{}))
  25. t.Cleanup(func() {
  26. DB = origDB
  27. LOG_DB = origLogDB
  28. sqlDB.Close()
  29. })
  30. return db
  31. }
  32. func TestRechargeWechat_Success(t *testing.T) {
  33. db := setupWechatTopUpDB(t)
  34. // 创建测试用户
  35. user := User{
  36. Id: 200,
  37. Username: "wechat_test_user",
  38. Password: "hashed_password",
  39. Quota: 100000,
  40. AffCode: "aff_wx_200",
  41. }
  42. require.NoError(t, db.Create(&user).Error)
  43. // 创建 pending 状态的充值订单
  44. expectedQuota := int(decimal.NewFromFloat(10.0).Mul(decimal.NewFromFloat(common.QuotaPerUnit)).IntPart())
  45. topUp := &TopUp{
  46. UserId: 200,
  47. Amount: 10,
  48. Money: 10.0, // 10 元
  49. TradeNo: "wx_test_trade_001",
  50. PaymentMethod: "wechat_pay",
  51. CreateTime: common.GetTimestamp(),
  52. Status: common.TopUpStatusPending,
  53. }
  54. require.NoError(t, db.Create(topUp).Error)
  55. // 执行充值
  56. err := RechargeWechat("wx_test_trade_001")
  57. require.NoError(t, err)
  58. // 验证用户额度已增加
  59. var updatedUser User
  60. require.NoError(t, db.First(&updatedUser, 200).Error)
  61. assert.Equal(t, 100000+expectedQuota, updatedUser.Quota)
  62. // 验证订单状态已更新
  63. var updatedTopUp TopUp
  64. require.NoError(t, db.Where("trade_no = ?", "wx_test_trade_001").First(&updatedTopUp).Error)
  65. assert.Equal(t, common.TopUpStatusSuccess, updatedTopUp.Status)
  66. assert.NotZero(t, updatedTopUp.CompleteTime)
  67. }
  68. func TestRechargeWechat_OrderNotFound(t *testing.T) {
  69. setupWechatTopUpDB(t)
  70. err := RechargeWechat("non_existent_trade_no")
  71. assert.Error(t, err)
  72. assert.Contains(t, err.Error(), "充值订单不存在")
  73. }
  74. func TestRechargeWechat_EmptyTradeNo(t *testing.T) {
  75. setupWechatTopUpDB(t)
  76. err := RechargeWechat("")
  77. assert.Error(t, err)
  78. assert.Contains(t, err.Error(), "未提供支付单号")
  79. }
  80. func TestRechargeWechat_AlreadyCompleted(t *testing.T) {
  81. db := setupWechatTopUpDB(t)
  82. // 创建测试用户
  83. user := User{
  84. Id: 201,
  85. Username: "wechat_test_user_2",
  86. Password: "hashed_password",
  87. Quota: 100000,
  88. AffCode: "aff_wx_201",
  89. }
  90. require.NoError(t, db.Create(&user).Error)
  91. // 创建已完成的充值订单
  92. topUp := &TopUp{
  93. UserId: 201,
  94. Amount: 10,
  95. Money: 10.0,
  96. TradeNo: "wx_test_trade_completed",
  97. PaymentMethod: "wechat_pay",
  98. CreateTime: common.GetTimestamp(),
  99. CompleteTime: common.GetTimestamp(),
  100. Status: common.TopUpStatusSuccess,
  101. }
  102. require.NoError(t, db.Create(topUp).Error)
  103. // 再次充值应幂等返回成功
  104. err := RechargeWechat("wx_test_trade_completed")
  105. require.NoError(t, err)
  106. // 验证额度未变化
  107. var updatedUser User
  108. require.NoError(t, db.First(&updatedUser, 201).Error)
  109. assert.Equal(t, 100000, updatedUser.Quota) // 额度不变
  110. }
  111. func TestRechargeWechat_InvalidStatus(t *testing.T) {
  112. db := setupWechatTopUpDB(t)
  113. // 创建测试用户
  114. user := User{
  115. Id: 202,
  116. Username: "wechat_test_user_3",
  117. Password: "hashed_password",
  118. Quota: 100000,
  119. AffCode: "aff_wx_202",
  120. }
  121. require.NoError(t, db.Create(&user).Error)
  122. // 创建 expired 状态的订单
  123. topUp := &TopUp{
  124. UserId: 202,
  125. Amount: 10,
  126. Money: 10.0,
  127. TradeNo: "wx_test_trade_expired",
  128. PaymentMethod: "wechat_pay",
  129. CreateTime: common.GetTimestamp(),
  130. Status: common.TopUpStatusExpired,
  131. }
  132. require.NoError(t, db.Create(topUp).Error)
  133. // 应返回状态错误
  134. err := RechargeWechat("wx_test_trade_expired")
  135. assert.Error(t, err)
  136. assert.Contains(t, err.Error(), "充值订单状态错误")
  137. // 验证额度未变化
  138. var updatedUser User
  139. require.NoError(t, db.First(&updatedUser, 202).Error)
  140. assert.Equal(t, 100000, updatedUser.Quota)
  141. }
  142. func TestRechargeWechat_ConcurrentSafety(t *testing.T) {
  143. db := setupWechatTopUpDB(t)
  144. // 创建测试用户
  145. user := User{
  146. Id: 203,
  147. Username: "wechat_concurrent_user",
  148. Password: "hashed_password",
  149. Quota: 100000,
  150. AffCode: "aff_wx_203",
  151. }
  152. require.NoError(t, db.Create(&user).Error)
  153. expectedQuota := int(decimal.NewFromFloat(10.0).Mul(decimal.NewFromFloat(common.QuotaPerUnit)).IntPart())
  154. // 创建 pending 订单
  155. topUp := &TopUp{
  156. UserId: 203,
  157. Amount: 10,
  158. Money: 10.0,
  159. TradeNo: "wx_concurrent_trade",
  160. PaymentMethod: "wechat_pay",
  161. CreateTime: common.GetTimestamp(),
  162. Status: common.TopUpStatusPending,
  163. }
  164. require.NoError(t, db.Create(topUp).Error)
  165. // 并发调用 RechargeWechat,只有第一个应该成功充值
  166. done := make(chan error, 2)
  167. go func() { done <- RechargeWechat("wx_concurrent_trade") }()
  168. go func() { done <- RechargeWechat("wx_concurrent_trade") }()
  169. err1 := <-done
  170. err2 := <-done
  171. // 至少一个成功,不能两个都失败
  172. require.True(t, err1 == nil || err2 == nil, "至少一个并发请求应成功")
  173. // 验证额度只增加了一次(因为 SQLite 单连接,实际上是序列化的)
  174. var updatedUser User
  175. require.NoError(t, db.First(&updatedUser, 203).Error)
  176. assert.Equal(t, 100000+expectedQuota, updatedUser.Quota, "额度应只增加一次")
  177. }
  178. func TestRechargeWechat_QuotaCalculation(t *testing.T) {
  179. db := setupWechatTopUpDB(t)
  180. testCases := []struct {
  181. name string
  182. money float64
  183. initialQuota int
  184. expectedAdd int
  185. userId int
  186. }{
  187. {"整数金额", 7.0, 0, int(decimal.NewFromFloat(7.0).Mul(decimal.NewFromFloat(common.QuotaPerUnit)).IntPart()), 310},
  188. {"小数金额", 7.5, 0, int(decimal.NewFromFloat(7.5).Mul(decimal.NewFromFloat(common.QuotaPerUnit)).IntPart()), 311},
  189. {"最小金额", 0.01, 0, int(decimal.NewFromFloat(0.01).Mul(decimal.NewFromFloat(common.QuotaPerUnit)).IntPart()), 312},
  190. }
  191. for _, tc := range testCases {
  192. t.Run(tc.name, func(t *testing.T) {
  193. userId := tc.userId
  194. tradeNo := "wx_quota_test_" + tc.name
  195. // 创建用户
  196. user := User{
  197. Id: userId,
  198. Username: "quota_test_" + tc.name,
  199. Password: "hashed",
  200. Quota: tc.initialQuota,
  201. AffCode: "aff_" + tc.name,
  202. }
  203. require.NoError(t, db.Create(&user).Error)
  204. // 创建订单
  205. topUp := &TopUp{
  206. UserId: userId,
  207. Amount: 1,
  208. Money: tc.money,
  209. TradeNo: tradeNo,
  210. PaymentMethod: "wechat_pay",
  211. CreateTime: common.GetTimestamp(),
  212. Status: common.TopUpStatusPending,
  213. }
  214. require.NoError(t, db.Create(topUp).Error)
  215. err := RechargeWechat(tradeNo)
  216. require.NoError(t, err)
  217. var updatedUser User
  218. require.NoError(t, db.First(&updatedUser, userId).Error)
  219. assert.Equal(t, tc.initialQuota+tc.expectedAdd, updatedUser.Quota, tc.name)
  220. })
  221. }
  222. }