package region_sync import ( "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/glebarez/sqlite" "gorm.io/gorm" "github.com/QuantumNous/new-api/model" "github.com/QuantumNous/new-api/setting/system_setting" ) // setupTestDB 设置测试数据库 func setupTestDB(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) // 迁移模型 err = db.AutoMigrate(&model.PendingSyncRecord{}) require.NoError(t, err) return db } func TestSyncManager_NewSyncManager(t *testing.T) { manager := NewSyncManager() assert.NotNil(t, manager.client) } func TestSyncManager_RunBatchSync_Empty(t *testing.T) { // 保存原始设置 settings := system_setting.GetRegionSyncSettings() settings.Enabled = false manager := NewSyncManager() count := manager.RunBatchSync() assert.Equal(t, 0, count) } func TestSyncManager_RunBatchSync_WithRecords(t *testing.T) { origDB := model.DB defer func() { model.DB = origDB }() // 设置测试数据库 db := setupTestDB(t) model.DB = db // 插入一些待同步记录 record1 := &model.PendingSyncRecord{ Id: 1, UserId: 100, RemoteUserId: 100, RequestId: "req-001", Quota: 100, Status: model.PendingSyncStatusSettled, CreatedAt: time.Now().Unix(), } record2 := &model.PendingSyncRecord{ Id: 2, UserId: 100, RemoteUserId: 100, RequestId: "req-002", Quota: 200, Status: model.PendingSyncStatusSettled, CreatedAt: time.Now().Unix(), } require.NoError(t, db.Create(record1).Error) require.NoError(t, db.Create(record2).Error) manager := NewSyncManager() count := manager.RunBatchSync() // 因为没有真正的服务器,返回 0 assert.Equal(t, 0, count) } func TestSyncManager_QueryMasterQuota(t *testing.T) { origDB := model.DB defer func() { model.DB = origDB }() manager := NewSyncManager() quota, err := manager.QueryMasterQuota(100) // 在测试环境中应该返回错误(因为没有启用配置) require.Error(t, err) assert.Equal(t, 0, quota) } func TestSyncManager_RunCleanup(t *testing.T) { origDB := model.DB defer func() { model.DB = origDB }() // 设置测试数据库 db := setupTestDB(t) model.DB = db // 插入一些已同步的旧记录 oldRecord := &model.PendingSyncRecord{ Id: 1, UserId: 100, RemoteUserId: 100, RequestId: "req-001", Quota: 100, Status: model.PendingSyncStatusSynced, CreatedAt: time.Now().Add(-40 * 24 * time.Hour).Unix(), } require.NoError(t, db.Create(oldRecord).Error) manager := NewSyncManager() deletedCount := manager.RunCleanup() assert.GreaterOrEqual(t, deletedCount, 0) } func TestSyncManager_RunBatchSync_MultipleRecords(t *testing.T) { origDB := model.DB defer func() { model.DB = origDB }() // 设置测试数据库 db := setupTestDB(t) model.DB = db // 插入待同步记录 for i := 1; i <= 3; i++ { record := &model.PendingSyncRecord{ Id: i, UserId: 100, RemoteUserId: 100, RequestId: fmt.Sprintf("req-batch-%d", i), Quota: i * 100, Status: model.PendingSyncStatusSettled, CreatedAt: time.Now().Unix(), } require.NoError(t, db.Create(record).Error) } manager := NewSyncManager() // 执行批量同步 count := manager.RunBatchSync() // 因为没有真正的服务器,返回 0 assert.Equal(t, 0, count) } func TestSyncManager_StartSyncWorkers(t *testing.T) { manager := NewSyncManager() // 这个测试只是验证不会 panic // 实际的协程测试需要更复杂的设置 manager.StartSyncWorkers() // 等待一小段时间让协程启动 time.Sleep(100 * time.Millisecond) // 停止管理器 manager.Stop() } // --------------------------------------------------------------------------- // 阶段 3 补充:SyncManager 端到端测试 // --------------------------------------------------------------------------- func TestRunBatchSync_WithMockServer(t *testing.T) { origDB := model.DB defer func() { model.DB = origDB }() db := setupTestDB(t) model.DB = db now := time.Now().Unix() settings := system_setting.GetRegionSyncSettings() origEnabled := settings.Enabled origBatchSize := settings.SyncBatchSize origMaxRetry := settings.MaxRetryCount defer func() { settings.Enabled = origEnabled settings.SyncBatchSize = origBatchSize settings.MaxRetryCount = origMaxRetry }() settings.Enabled = true settings.IsMaster = true // 跳过 FetchConfig settings.SyncBatchSize = 100 settings.MaxRetryCount = 3 t.Run("AllSuccess", func(t *testing.T) { require.NoError(t, db.Exec("DELETE FROM pending_sync_records").Error) require.NoError(t, db.Create(&model.PendingSyncRecord{ UserId: 100, RemoteUserId: 200, RequestId: "req-batch-ok-1", Quota: 100, Status: model.PendingSyncStatusSettled, CreatedAt: now, }).Error) require.NoError(t, db.Create(&model.PendingSyncRecord{ UserId: 101, RemoteUserId: 201, RequestId: "req-batch-ok-2", Quota: 200, Status: model.PendingSyncStatusSettled, CreatedAt: now + 1, }).Error) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(BatchDeductResponse{ Success: true, Results: []DeductResult{ {UserId: 200, Success: true, DeductedQuota: 100, RemainingQuota: 999}, {UserId: 201, Success: true, DeductedQuota: 200, RemainingQuota: 888}, }, }) })) defer server.Close() manager := &SyncManager{client: NewSyncClient(server.URL, "test-key")} count := manager.RunBatchSync() assert.Equal(t, 2, count) var syncedCount int64 db.Model(&model.PendingSyncRecord{}).Where("status = ?", model.PendingSyncStatusSynced).Count(&syncedCount) assert.Equal(t, int64(2), syncedCount) }) t.Run("PartialSuccess", func(t *testing.T) { require.NoError(t, db.Exec("DELETE FROM pending_sync_records").Error) require.NoError(t, db.Create(&model.PendingSyncRecord{ UserId: 100, RemoteUserId: 200, RequestId: "req-part-1", Quota: 100, Status: model.PendingSyncStatusSettled, CreatedAt: now, }).Error) require.NoError(t, db.Create(&model.PendingSyncRecord{ UserId: 101, RemoteUserId: 201, RequestId: "req-part-2", Quota: 200, Status: model.PendingSyncStatusSettled, CreatedAt: now + 1, }).Error) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(BatchDeductResponse{ Success: true, Results: []DeductResult{ {UserId: 200, Success: true, DeductedQuota: 100}, {UserId: 201, Success: false, Error: "insufficient quota"}, }, }) })) defer server.Close() manager := &SyncManager{client: NewSyncClient(server.URL, "test-key")} count := manager.RunBatchSync() assert.Equal(t, 1, count) var syncedCount, pendingCount int64 db.Model(&model.PendingSyncRecord{}).Where("status = ?", model.PendingSyncStatusSynced).Count(&syncedCount) db.Model(&model.PendingSyncRecord{}).Where("status = ?", model.PendingSyncStatusSettled).Count(&pendingCount) assert.Equal(t, int64(1), syncedCount) assert.Equal(t, int64(1), pendingCount) }) t.Run("ServerError", func(t *testing.T) { require.NoError(t, db.Exec("DELETE FROM pending_sync_records").Error) require.NoError(t, db.Create(&model.PendingSyncRecord{ UserId: 100, RemoteUserId: 200, RequestId: "req-srv-err", Quota: 100, Status: model.PendingSyncStatusSettled, CreatedAt: now, }).Error) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(500) json.NewEncoder(w).Encode(BatchDeductResponse{Success: false, Error: "internal error"}) })) defer server.Close() manager := &SyncManager{client: NewSyncClient(server.URL, "test-key")} count := manager.RunBatchSync() assert.Equal(t, 0, count) }) } func TestRunBatchSync_RetryExhaustion(t *testing.T) { origDB := model.DB defer func() { model.DB = origDB }() db := setupTestDB(t) model.DB = db now := time.Now().Unix() require.NoError(t, db.Create(&model.PendingSyncRecord{ UserId: 100, RemoteUserId: 200, RequestId: "req-retry-exh", Quota: 100, Status: model.PendingSyncStatusFailed, RetryCount: 2, CreatedAt: now, }).Error) settings := system_setting.GetRegionSyncSettings() origEnabled := settings.Enabled origBatchSize := settings.SyncBatchSize origMaxRetry := settings.MaxRetryCount defer func() { settings.Enabled = origEnabled settings.SyncBatchSize = origBatchSize settings.MaxRetryCount = origMaxRetry }() settings.Enabled = true settings.IsMaster = true settings.SyncBatchSize = 100 settings.MaxRetryCount = 3 manager := &SyncManager{client: NewSyncClient("http://127.0.0.1:0", "key")} count := manager.RunBatchSync() assert.Equal(t, 0, count) var record model.PendingSyncRecord require.NoError(t, db.Where("request_id = ?", "req-retry-exh").First(&record).Error) assert.Equal(t, model.PendingSyncStatusFailed, record.Status) assert.Equal(t, 3, record.RetryCount) }