You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

213 lines
5.9 KiB

  1. package service
  2. import (
  3. "testing"
  4. "time"
  5. "github.com/QuantumNous/new-api/common"
  6. "github.com/QuantumNous/new-api/model"
  7. "github.com/QuantumNous/new-api/setting/system_setting"
  8. "github.com/glebarez/sqlite"
  9. "github.com/stretchr/testify/assert"
  10. "github.com/stretchr/testify/require"
  11. "gorm.io/gorm"
  12. )
  13. func setupSyncedFundingDB(t *testing.T) *gorm.DB {
  14. t.Helper()
  15. db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
  16. require.NoError(t, err)
  17. sqlDB, _ := db.DB()
  18. sqlDB.SetMaxOpenConns(1)
  19. origDB := model.DB
  20. origLogDB := model.LOG_DB
  21. model.DB = db
  22. model.LOG_DB = db
  23. common.UsingSQLite = true
  24. common.RedisEnabled = false
  25. require.NoError(t, db.AutoMigrate(&model.PendingSyncRecord{}))
  26. t.Cleanup(func() {
  27. model.DB = origDB
  28. model.LOG_DB = origLogDB
  29. sqlDB.Close()
  30. })
  31. return db
  32. }
  33. func TestNewSyncedUserFunding(t *testing.T) {
  34. f := NewSyncedUserFunding(100, 200, "req-001", 500000)
  35. assert.Equal(t, 100, f.userId)
  36. assert.Equal(t, 200, f.remoteUserId)
  37. assert.Equal(t, "req-001", f.requestId)
  38. assert.Equal(t, 500000, f.syncedQuota)
  39. assert.Equal(t, 0, f.consumed)
  40. }
  41. func TestSyncedUserFunding_Source(t *testing.T) {
  42. f := NewSyncedUserFunding(100, 200, "req-001", 500000)
  43. assert.Equal(t, BillingSourceSyncedWallet, f.Source())
  44. }
  45. func TestPreConsume_Success(t *testing.T) {
  46. setupSyncedFundingDB(t)
  47. settings := system_setting.GetRegionSyncSettings()
  48. origThreshold := settings.MinBalanceThreshold
  49. settings.MinBalanceThreshold = 100000
  50. defer func() { settings.MinBalanceThreshold = origThreshold }()
  51. f := NewSyncedUserFunding(100, 200, "req-pre-success", 1000000)
  52. err := f.PreConsume(50000)
  53. require.NoError(t, err)
  54. assert.Equal(t, 50000, f.consumed)
  55. // 验证 PendingSyncRecord 已创建
  56. var record model.PendingSyncRecord
  57. require.NoError(t, model.DB.Where("request_id = ?", "req-pre-success").First(&record).Error)
  58. assert.Equal(t, 100, record.UserId)
  59. assert.Equal(t, 200, record.RemoteUserId)
  60. assert.Equal(t, 50000, record.Quota)
  61. assert.Equal(t, model.PendingSyncStatusPending, record.Status)
  62. }
  63. func TestPreConsume_ZeroAmount(t *testing.T) {
  64. setupSyncedFundingDB(t)
  65. f := NewSyncedUserFunding(100, 200, "req-pre-zero", 1000000)
  66. err := f.PreConsume(0)
  67. assert.NoError(t, err)
  68. assert.Equal(t, 0, f.consumed)
  69. }
  70. func TestPreConsume_NegativeAmount(t *testing.T) {
  71. setupSyncedFundingDB(t)
  72. f := NewSyncedUserFunding(100, 200, "req-pre-neg", 1000000)
  73. err := f.PreConsume(-100)
  74. assert.NoError(t, err)
  75. assert.Equal(t, 0, f.consumed)
  76. }
  77. func TestPreConsume_InsufficientQuota(t *testing.T) {
  78. setupSyncedFundingDB(t)
  79. settings := system_setting.GetRegionSyncSettings()
  80. origThreshold := settings.MinBalanceThreshold
  81. settings.MinBalanceThreshold = 100000
  82. defer func() { settings.MinBalanceThreshold = origThreshold }()
  83. // syncedQuota=150000, amount=50000, threshold=100000
  84. // 150000 < 50000 + 100000 = 150000 → 不满足(严格小于)
  85. f := NewSyncedUserFunding(100, 200, "req-pre-insuf", 149999)
  86. err := f.PreConsume(50000)
  87. assert.Error(t, err)
  88. assert.Contains(t, err.Error(), "余额不足")
  89. assert.Equal(t, 0, f.consumed)
  90. }
  91. func TestPreConsume_ExactlyAtThreshold(t *testing.T) {
  92. setupSyncedFundingDB(t)
  93. settings := system_setting.GetRegionSyncSettings()
  94. origThreshold := settings.MinBalanceThreshold
  95. settings.MinBalanceThreshold = 100000
  96. defer func() { settings.MinBalanceThreshold = origThreshold }()
  97. // syncedQuota=150000, amount=50000, threshold=100000
  98. // 150000 >= 50000 + 100000 = 150000 → 刚好满足
  99. f := NewSyncedUserFunding(100, 200, "req-pre-exact", 150000)
  100. err := f.PreConsume(50000)
  101. assert.NoError(t, err)
  102. assert.Equal(t, 50000, f.consumed)
  103. }
  104. func TestSettle_PositiveDelta(t *testing.T) {
  105. f := NewSyncedUserFunding(100, 200, "req-settle-pos", 500000)
  106. f.consumed = 1000
  107. err := f.Settle(500)
  108. assert.NoError(t, err)
  109. assert.Equal(t, 1500, f.consumed)
  110. assert.Equal(t, 499500, f.syncedQuota) // 500000 - 500
  111. }
  112. func TestSettle_NegativeDelta(t *testing.T) {
  113. f := NewSyncedUserFunding(100, 200, "req-settle-neg", 500000)
  114. f.consumed = 1000
  115. err := f.Settle(-300)
  116. assert.NoError(t, err)
  117. assert.Equal(t, 700, f.consumed)
  118. assert.Equal(t, 500300, f.syncedQuota) // 500000 - (-300) = 500000 + 300
  119. }
  120. func TestSettle_ZeroDelta(t *testing.T) {
  121. f := NewSyncedUserFunding(100, 200, "req-settle-zero", 500000)
  122. f.consumed = 1000
  123. err := f.Settle(0)
  124. assert.NoError(t, err)
  125. assert.Equal(t, 1000, f.consumed) // 不变
  126. assert.Equal(t, 500000, f.syncedQuota) // 不变
  127. }
  128. func TestRefund_Success(t *testing.T) {
  129. db := setupSyncedFundingDB(t)
  130. // 先创建一条 pending 记录
  131. require.NoError(t, model.CreatePendingSyncRecord(100, 200, "req-refund-ok", 5000, 5000))
  132. f := NewSyncedUserFunding(100, 200, "req-refund-ok", 500000)
  133. f.consumed = 5000
  134. err := f.Refund()
  135. assert.NoError(t, err)
  136. // 验证记录已被标记为 synced
  137. var record model.PendingSyncRecord
  138. require.NoError(t, db.Where("request_id = ?", "req-refund-ok").First(&record).Error)
  139. assert.Equal(t, model.PendingSyncStatusSynced, record.Status)
  140. assert.NotZero(t, record.SyncedAt)
  141. }
  142. func TestRefund_NoConsumed(t *testing.T) {
  143. setupSyncedFundingDB(t)
  144. f := NewSyncedUserFunding(100, 200, "req-refund-nocons", 500000)
  145. f.consumed = 0
  146. err := f.Refund()
  147. assert.NoError(t, err)
  148. }
  149. func TestRefund_RecordNotFound(t *testing.T) {
  150. setupSyncedFundingDB(t)
  151. // 没有 PendingSyncRecord,但 consumed > 0
  152. f := NewSyncedUserFunding(100, 200, "req-refund-notfound", 500000)
  153. f.consumed = 5000
  154. err := f.Refund()
  155. assert.NoError(t, err) // 应优雅处理,不报错
  156. }
  157. func TestRefund_RecordAlreadySynced(t *testing.T) {
  158. db := setupSyncedFundingDB(t)
  159. // 创建已 synced 的记录
  160. require.NoError(t, db.Create(&model.PendingSyncRecord{
  161. UserId: 100, RemoteUserId: 200, RequestId: "req-refund-synced",
  162. Quota: 5000, Status: model.PendingSyncStatusSynced,
  163. SyncedAt: time.Now().Unix(), CreatedAt: time.Now().Unix(),
  164. }).Error)
  165. f := NewSyncedUserFunding(100, 200, "req-refund-synced", 500000)
  166. f.consumed = 5000
  167. err := f.Refund()
  168. assert.NoError(t, err) // status=pending 不匹配,优雅处理
  169. }