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.
 
 
 

327 lines
9.2 KiB

  1. package region_sync
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "net/http/httptest"
  7. "testing"
  8. "time"
  9. "github.com/stretchr/testify/assert"
  10. "github.com/stretchr/testify/require"
  11. "github.com/glebarez/sqlite"
  12. "gorm.io/gorm"
  13. "github.com/QuantumNous/new-api/model"
  14. "github.com/QuantumNous/new-api/setting/system_setting"
  15. )
  16. // setupTestDB 设置测试数据库
  17. func setupTestDB(t *testing.T) *gorm.DB {
  18. t.Helper()
  19. db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
  20. require.NoError(t, err)
  21. sqlDB, _ := db.DB()
  22. sqlDB.SetMaxOpenConns(1)
  23. // 迁移模型
  24. err = db.AutoMigrate(&model.PendingSyncRecord{})
  25. require.NoError(t, err)
  26. return db
  27. }
  28. func TestSyncManager_NewSyncManager(t *testing.T) {
  29. manager := NewSyncManager()
  30. assert.NotNil(t, manager.client)
  31. }
  32. func TestSyncManager_RunBatchSync_Empty(t *testing.T) {
  33. // 保存原始设置
  34. settings := system_setting.GetRegionSyncSettings()
  35. settings.Enabled = false
  36. manager := NewSyncManager()
  37. count := manager.RunBatchSync()
  38. assert.Equal(t, 0, count)
  39. }
  40. func TestSyncManager_RunBatchSync_WithRecords(t *testing.T) {
  41. origDB := model.DB
  42. defer func() {
  43. model.DB = origDB
  44. }()
  45. // 设置测试数据库
  46. db := setupTestDB(t)
  47. model.DB = db
  48. // 插入一些待同步记录
  49. record1 := &model.PendingSyncRecord{
  50. Id: 1,
  51. UserId: 100,
  52. RemoteUserId: 100,
  53. RequestId: "req-001",
  54. Quota: 100,
  55. Status: model.PendingSyncStatusSettled,
  56. CreatedAt: time.Now().Unix(),
  57. }
  58. record2 := &model.PendingSyncRecord{
  59. Id: 2,
  60. UserId: 100,
  61. RemoteUserId: 100,
  62. RequestId: "req-002",
  63. Quota: 200,
  64. Status: model.PendingSyncStatusSettled,
  65. CreatedAt: time.Now().Unix(),
  66. }
  67. require.NoError(t, db.Create(record1).Error)
  68. require.NoError(t, db.Create(record2).Error)
  69. manager := NewSyncManager()
  70. count := manager.RunBatchSync()
  71. // 因为没有真正的服务器,返回 0
  72. assert.Equal(t, 0, count)
  73. }
  74. func TestSyncManager_QueryMasterQuota(t *testing.T) {
  75. origDB := model.DB
  76. defer func() {
  77. model.DB = origDB
  78. }()
  79. manager := NewSyncManager()
  80. quota, err := manager.QueryMasterQuota(100)
  81. // 在测试环境中应该返回错误(因为没有启用配置)
  82. require.Error(t, err)
  83. assert.Equal(t, 0, quota)
  84. }
  85. func TestSyncManager_RunCleanup(t *testing.T) {
  86. origDB := model.DB
  87. defer func() {
  88. model.DB = origDB
  89. }()
  90. // 设置测试数据库
  91. db := setupTestDB(t)
  92. model.DB = db
  93. // 插入一些已同步的旧记录
  94. oldRecord := &model.PendingSyncRecord{
  95. Id: 1,
  96. UserId: 100,
  97. RemoteUserId: 100,
  98. RequestId: "req-001",
  99. Quota: 100,
  100. Status: model.PendingSyncStatusSynced,
  101. CreatedAt: time.Now().Add(-40 * 24 * time.Hour).Unix(),
  102. }
  103. require.NoError(t, db.Create(oldRecord).Error)
  104. manager := NewSyncManager()
  105. deletedCount := manager.RunCleanup()
  106. assert.GreaterOrEqual(t, deletedCount, 0)
  107. }
  108. func TestSyncManager_RunBatchSync_MultipleRecords(t *testing.T) {
  109. origDB := model.DB
  110. defer func() {
  111. model.DB = origDB
  112. }()
  113. // 设置测试数据库
  114. db := setupTestDB(t)
  115. model.DB = db
  116. // 插入待同步记录
  117. for i := 1; i <= 3; i++ {
  118. record := &model.PendingSyncRecord{
  119. Id: i,
  120. UserId: 100,
  121. RemoteUserId: 100,
  122. RequestId: fmt.Sprintf("req-batch-%d", i),
  123. Quota: i * 100,
  124. Status: model.PendingSyncStatusSettled,
  125. CreatedAt: time.Now().Unix(),
  126. }
  127. require.NoError(t, db.Create(record).Error)
  128. }
  129. manager := NewSyncManager()
  130. // 执行批量同步
  131. count := manager.RunBatchSync()
  132. // 因为没有真正的服务器,返回 0
  133. assert.Equal(t, 0, count)
  134. }
  135. func TestSyncManager_StartSyncWorkers(t *testing.T) {
  136. manager := NewSyncManager()
  137. // 这个测试只是验证不会 panic
  138. // 实际的协程测试需要更复杂的设置
  139. manager.StartSyncWorkers()
  140. // 等待一小段时间让协程启动
  141. time.Sleep(100 * time.Millisecond)
  142. // 停止管理器
  143. manager.Stop()
  144. }
  145. // ---------------------------------------------------------------------------
  146. // 阶段 3 补充:SyncManager 端到端测试
  147. // ---------------------------------------------------------------------------
  148. func TestRunBatchSync_WithMockServer(t *testing.T) {
  149. origDB := model.DB
  150. defer func() { model.DB = origDB }()
  151. db := setupTestDB(t)
  152. model.DB = db
  153. now := time.Now().Unix()
  154. settings := system_setting.GetRegionSyncSettings()
  155. origEnabled := settings.Enabled
  156. origBatchSize := settings.SyncBatchSize
  157. origMaxRetry := settings.MaxRetryCount
  158. defer func() {
  159. settings.Enabled = origEnabled
  160. settings.SyncBatchSize = origBatchSize
  161. settings.MaxRetryCount = origMaxRetry
  162. }()
  163. settings.Enabled = true
  164. settings.IsMaster = true // 跳过 FetchConfig
  165. settings.SyncBatchSize = 100
  166. settings.MaxRetryCount = 3
  167. t.Run("AllSuccess", func(t *testing.T) {
  168. require.NoError(t, db.Exec("DELETE FROM pending_sync_records").Error)
  169. require.NoError(t, db.Create(&model.PendingSyncRecord{
  170. UserId: 100, RemoteUserId: 200, RequestId: "req-batch-ok-1",
  171. Quota: 100, Status: model.PendingSyncStatusSettled, CreatedAt: now,
  172. }).Error)
  173. require.NoError(t, db.Create(&model.PendingSyncRecord{
  174. UserId: 101, RemoteUserId: 201, RequestId: "req-batch-ok-2",
  175. Quota: 200, Status: model.PendingSyncStatusSettled, CreatedAt: now + 1,
  176. }).Error)
  177. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  178. w.Header().Set("Content-Type", "application/json")
  179. json.NewEncoder(w).Encode(BatchDeductResponse{
  180. Success: true,
  181. Results: []DeductResult{
  182. {UserId: 200, Success: true, DeductedQuota: 100, RemainingQuota: 999},
  183. {UserId: 201, Success: true, DeductedQuota: 200, RemainingQuota: 888},
  184. },
  185. })
  186. }))
  187. defer server.Close()
  188. manager := &SyncManager{client: NewSyncClient(server.URL, "test-key")}
  189. count := manager.RunBatchSync()
  190. assert.Equal(t, 2, count)
  191. var syncedCount int64
  192. db.Model(&model.PendingSyncRecord{}).Where("status = ?", model.PendingSyncStatusSynced).Count(&syncedCount)
  193. assert.Equal(t, int64(2), syncedCount)
  194. })
  195. t.Run("PartialSuccess", func(t *testing.T) {
  196. require.NoError(t, db.Exec("DELETE FROM pending_sync_records").Error)
  197. require.NoError(t, db.Create(&model.PendingSyncRecord{
  198. UserId: 100, RemoteUserId: 200, RequestId: "req-part-1",
  199. Quota: 100, Status: model.PendingSyncStatusSettled, CreatedAt: now,
  200. }).Error)
  201. require.NoError(t, db.Create(&model.PendingSyncRecord{
  202. UserId: 101, RemoteUserId: 201, RequestId: "req-part-2",
  203. Quota: 200, Status: model.PendingSyncStatusSettled, CreatedAt: now + 1,
  204. }).Error)
  205. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  206. w.Header().Set("Content-Type", "application/json")
  207. json.NewEncoder(w).Encode(BatchDeductResponse{
  208. Success: true,
  209. Results: []DeductResult{
  210. {UserId: 200, Success: true, DeductedQuota: 100},
  211. {UserId: 201, Success: false, Error: "insufficient quota"},
  212. },
  213. })
  214. }))
  215. defer server.Close()
  216. manager := &SyncManager{client: NewSyncClient(server.URL, "test-key")}
  217. count := manager.RunBatchSync()
  218. assert.Equal(t, 1, count)
  219. var syncedCount, pendingCount int64
  220. db.Model(&model.PendingSyncRecord{}).Where("status = ?", model.PendingSyncStatusSynced).Count(&syncedCount)
  221. db.Model(&model.PendingSyncRecord{}).Where("status = ?", model.PendingSyncStatusSettled).Count(&pendingCount)
  222. assert.Equal(t, int64(1), syncedCount)
  223. assert.Equal(t, int64(1), pendingCount)
  224. })
  225. t.Run("ServerError", func(t *testing.T) {
  226. require.NoError(t, db.Exec("DELETE FROM pending_sync_records").Error)
  227. require.NoError(t, db.Create(&model.PendingSyncRecord{
  228. UserId: 100, RemoteUserId: 200, RequestId: "req-srv-err",
  229. Quota: 100, Status: model.PendingSyncStatusSettled, CreatedAt: now,
  230. }).Error)
  231. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  232. w.Header().Set("Content-Type", "application/json")
  233. w.WriteHeader(500)
  234. json.NewEncoder(w).Encode(BatchDeductResponse{Success: false, Error: "internal error"})
  235. }))
  236. defer server.Close()
  237. manager := &SyncManager{client: NewSyncClient(server.URL, "test-key")}
  238. count := manager.RunBatchSync()
  239. assert.Equal(t, 0, count)
  240. })
  241. }
  242. func TestRunBatchSync_RetryExhaustion(t *testing.T) {
  243. origDB := model.DB
  244. defer func() { model.DB = origDB }()
  245. db := setupTestDB(t)
  246. model.DB = db
  247. now := time.Now().Unix()
  248. require.NoError(t, db.Create(&model.PendingSyncRecord{
  249. UserId: 100, RemoteUserId: 200, RequestId: "req-retry-exh",
  250. Quota: 100, Status: model.PendingSyncStatusFailed, RetryCount: 2, CreatedAt: now,
  251. }).Error)
  252. settings := system_setting.GetRegionSyncSettings()
  253. origEnabled := settings.Enabled
  254. origBatchSize := settings.SyncBatchSize
  255. origMaxRetry := settings.MaxRetryCount
  256. defer func() {
  257. settings.Enabled = origEnabled
  258. settings.SyncBatchSize = origBatchSize
  259. settings.MaxRetryCount = origMaxRetry
  260. }()
  261. settings.Enabled = true
  262. settings.IsMaster = true
  263. settings.SyncBatchSize = 100
  264. settings.MaxRetryCount = 3
  265. manager := &SyncManager{client: NewSyncClient("http://127.0.0.1:0", "key")}
  266. count := manager.RunBatchSync()
  267. assert.Equal(t, 0, count)
  268. var record model.PendingSyncRecord
  269. require.NoError(t, db.Where("request_id = ?", "req-retry-exh").First(&record).Error)
  270. assert.Equal(t, model.PendingSyncStatusFailed, record.Status)
  271. assert.Equal(t, 3, record.RetryCount)
  272. }