|
- package service
-
- import (
- "testing"
- "time"
-
- "github.com/QuantumNous/new-api/common"
- "github.com/QuantumNous/new-api/model"
- "github.com/QuantumNous/new-api/setting/system_setting"
- "github.com/glebarez/sqlite"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "gorm.io/gorm"
- )
-
- func setupSyncedFundingDB(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 := model.DB
- origLogDB := model.LOG_DB
- model.DB = db
- model.LOG_DB = db
- common.UsingSQLite = true
- common.RedisEnabled = false
-
- require.NoError(t, db.AutoMigrate(&model.PendingSyncRecord{}, &model.User{}))
-
- t.Cleanup(func() {
- model.DB = origDB
- model.LOG_DB = origLogDB
- sqlDB.Close()
- })
- return db
- }
-
- func TestNewSyncedUserFunding(t *testing.T) {
- f := NewSyncedUserFunding(100, 200, "req-001", 500000)
- assert.Equal(t, 100, f.userId)
- assert.Equal(t, 200, f.remoteUserId)
- assert.Equal(t, "req-001", f.requestId)
- assert.Equal(t, 500000, f.syncedQuota)
- assert.Equal(t, 0, f.consumed)
- }
-
- func TestSyncedUserFunding_Source(t *testing.T) {
- f := NewSyncedUserFunding(100, 200, "req-001", 500000)
- assert.Equal(t, BillingSourceSyncedWallet, f.Source())
- }
-
- func TestPreConsume_Success(t *testing.T) {
- setupSyncedFundingDB(t)
-
- settings := system_setting.GetRegionSyncSettings()
- origThreshold := settings.MinBalanceThreshold
- settings.MinBalanceThreshold = 100000
- defer func() { settings.MinBalanceThreshold = origThreshold }()
-
- // 创建测试用户(AtomicDecreaseSyncedQuota 操作 DB 中的 synced_quota)
- require.NoError(t, model.DB.Create(&model.User{
- Id: 100, Username: "test-synced", Source: common.UserSourceSynced, SyncedQuota: 1000000,
- }).Error)
-
- f := NewSyncedUserFunding(100, 200, "req-pre-success", 1000000)
- err := f.PreConsume(50000)
- require.NoError(t, err)
- assert.Equal(t, 50000, f.consumed)
-
- // 验证 PendingSyncRecord 已创建
- var record model.PendingSyncRecord
- require.NoError(t, model.DB.Where("request_id = ?", "req-pre-success").First(&record).Error)
- assert.Equal(t, 100, record.UserId)
- assert.Equal(t, 200, record.RemoteUserId)
- assert.Equal(t, 50000, record.Quota)
- assert.Equal(t, model.PendingSyncStatusPending, record.Status)
- }
-
- func TestPreConsume_ZeroAmount(t *testing.T) {
- setupSyncedFundingDB(t)
-
- f := NewSyncedUserFunding(100, 200, "req-pre-zero", 1000000)
- err := f.PreConsume(0)
- assert.NoError(t, err)
- assert.Equal(t, 0, f.consumed)
- }
-
- func TestPreConsume_NegativeAmount(t *testing.T) {
- setupSyncedFundingDB(t)
-
- f := NewSyncedUserFunding(100, 200, "req-pre-neg", 1000000)
- err := f.PreConsume(-100)
- assert.NoError(t, err)
- assert.Equal(t, 0, f.consumed)
- }
-
- func TestPreConsume_InsufficientQuota(t *testing.T) {
- setupSyncedFundingDB(t)
-
- settings := system_setting.GetRegionSyncSettings()
- origThreshold := settings.MinBalanceThreshold
- settings.MinBalanceThreshold = 100000
- defer func() { settings.MinBalanceThreshold = origThreshold }()
-
- require.NoError(t, model.DB.Create(&model.User{
- Id: 100, Username: "test-insuf", Source: common.UserSourceSynced, SyncedQuota: 149999,
- }).Error)
-
- // syncedQuota=149999, amount=50000, threshold=100000
- // 149999 < 50000 + 100000 = 150000 → 不满足
- f := NewSyncedUserFunding(100, 200, "req-pre-insuf", 149999)
- err := f.PreConsume(50000)
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "余额不足")
- assert.Equal(t, 0, f.consumed)
- }
-
- func TestPreConsume_ExactlyAtThreshold(t *testing.T) {
- setupSyncedFundingDB(t)
-
- settings := system_setting.GetRegionSyncSettings()
- origThreshold := settings.MinBalanceThreshold
- settings.MinBalanceThreshold = 100000
- defer func() { settings.MinBalanceThreshold = origThreshold }()
-
- require.NoError(t, model.DB.Create(&model.User{
- Id: 100, Username: "test-exact", Source: common.UserSourceSynced, SyncedQuota: 150000,
- }).Error)
-
- // syncedQuota=150000, amount=50000, threshold=100000
- // 150000 >= 50000 + 100000 = 150000 → 刚好满足
- f := NewSyncedUserFunding(100, 200, "req-pre-exact", 150000)
- err := f.PreConsume(50000)
- assert.NoError(t, err)
- assert.Equal(t, 50000, f.consumed)
- }
-
- func TestSettle_PositiveDelta(t *testing.T) {
- setupSyncedFundingDB(t)
-
- f := NewSyncedUserFunding(100, 200, "req-settle-pos", 500000)
- f.consumed = 1000
-
- err := f.Settle(500)
- assert.NoError(t, err)
- assert.Equal(t, 1500, f.consumed)
- assert.Equal(t, 499500, f.syncedQuota) // 500000 - 500
- }
-
- func TestSettle_NegativeDelta(t *testing.T) {
- setupSyncedFundingDB(t)
-
- f := NewSyncedUserFunding(100, 200, "req-settle-neg", 500000)
- f.consumed = 1000
-
- err := f.Settle(-300)
- assert.NoError(t, err)
- assert.Equal(t, 700, f.consumed)
- assert.Equal(t, 500300, f.syncedQuota) // 500000 - (-300) = 500000 + 300
- }
-
- func TestSettle_ZeroDelta(t *testing.T) {
- f := NewSyncedUserFunding(100, 200, "req-settle-zero", 500000)
- f.consumed = 1000
-
- err := f.Settle(0)
- assert.NoError(t, err)
- assert.Equal(t, 1000, f.consumed) // 不变
- assert.Equal(t, 500000, f.syncedQuota) // 不变
- }
-
- func TestSettle_TransitionsPendingToSettled(t *testing.T) {
- db := setupSyncedFundingDB(t)
-
- // 先创建 pending 记录
- require.NoError(t, model.CreatePendingSyncRecord(100, 200, "req-settle-trans", 1000, 1000))
-
- f := NewSyncedUserFunding(100, 200, "req-settle-trans", 500000)
- f.consumed = 1000
-
- err := f.Settle(-300) // 实际消费 700,退还 300
- assert.NoError(t, err)
- assert.Equal(t, 700, f.consumed)
-
- // 验证记录状态从 pending 转为 settled,quota 更新为实际消费
- var record model.PendingSyncRecord
- require.NoError(t, db.Where("request_id = ?", "req-settle-trans").First(&record).Error)
- assert.Equal(t, model.PendingSyncStatusSettled, record.Status)
- assert.Equal(t, 700, record.Quota)
- }
-
- func TestRefund_Success(t *testing.T) {
- db := setupSyncedFundingDB(t)
-
- // 创建测试用户
- require.NoError(t, model.DB.Create(&model.User{
- Id: 100, Username: "test-refund", Source: common.UserSourceSynced, SyncedQuota: 500000,
- }).Error)
-
- // 先创建一条 pending 记录
- require.NoError(t, model.CreatePendingSyncRecord(100, 200, "req-refund-ok", 5000, 5000))
-
- f := NewSyncedUserFunding(100, 200, "req-refund-ok", 500000)
- f.consumed = 5000
-
- err := f.Refund()
- assert.NoError(t, err)
-
- // 验证记录已被标记为 synced
- var record model.PendingSyncRecord
- require.NoError(t, db.Where("request_id = ?", "req-refund-ok").First(&record).Error)
- assert.Equal(t, model.PendingSyncStatusSynced, record.Status)
- assert.NotZero(t, record.SyncedAt)
- }
-
- func TestRefund_NoConsumed(t *testing.T) {
- setupSyncedFundingDB(t)
-
- f := NewSyncedUserFunding(100, 200, "req-refund-nocons", 500000)
- f.consumed = 0
-
- err := f.Refund()
- assert.NoError(t, err)
- }
-
- func TestRefund_RecordNotFound(t *testing.T) {
- setupSyncedFundingDB(t)
-
- // 没有 PendingSyncRecord,但 consumed > 0
- f := NewSyncedUserFunding(100, 200, "req-refund-notfound", 500000)
- f.consumed = 5000
-
- err := f.Refund()
- assert.NoError(t, err) // 应优雅处理,不报错
- }
-
- func TestRefund_RecordAlreadySynced(t *testing.T) {
- db := setupSyncedFundingDB(t)
-
- // 创建已 synced 的记录
- require.NoError(t, db.Create(&model.PendingSyncRecord{
- UserId: 100, RemoteUserId: 200, RequestId: "req-refund-synced",
- Quota: 5000, Status: model.PendingSyncStatusSynced,
- SyncedAt: time.Now().Unix(), CreatedAt: time.Now().Unix(),
- }).Error)
-
- f := NewSyncedUserFunding(100, 200, "req-refund-synced", 500000)
- f.consumed = 5000
-
- err := f.Refund()
- assert.NoError(t, err) // status=pending 不匹配,优雅处理
- }
|