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.
 
 
 

325 line
9.1 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.SyncBatchSize = 100
  165. settings.MaxRetryCount = 3
  166. t.Run("AllSuccess", func(t *testing.T) {
  167. require.NoError(t, db.Exec("DELETE FROM pending_sync_records").Error)
  168. require.NoError(t, db.Create(&model.PendingSyncRecord{
  169. UserId: 100, RemoteUserId: 200, RequestId: "req-batch-ok-1",
  170. Quota: 100, Status: model.PendingSyncStatusSettled, CreatedAt: now,
  171. }).Error)
  172. require.NoError(t, db.Create(&model.PendingSyncRecord{
  173. UserId: 101, RemoteUserId: 201, RequestId: "req-batch-ok-2",
  174. Quota: 200, Status: model.PendingSyncStatusSettled, CreatedAt: now + 1,
  175. }).Error)
  176. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  177. w.Header().Set("Content-Type", "application/json")
  178. json.NewEncoder(w).Encode(BatchDeductResponse{
  179. Success: true,
  180. Results: []DeductResult{
  181. {UserId: 200, Success: true, DeductedQuota: 100, RemainingQuota: 999},
  182. {UserId: 201, Success: true, DeductedQuota: 200, RemainingQuota: 888},
  183. },
  184. })
  185. }))
  186. defer server.Close()
  187. manager := &SyncManager{client: NewSyncClient(server.URL, "test-key")}
  188. count := manager.RunBatchSync()
  189. assert.Equal(t, 2, count)
  190. var syncedCount int64
  191. db.Model(&model.PendingSyncRecord{}).Where("status = ?", model.PendingSyncStatusSynced).Count(&syncedCount)
  192. assert.Equal(t, int64(2), syncedCount)
  193. })
  194. t.Run("PartialSuccess", func(t *testing.T) {
  195. require.NoError(t, db.Exec("DELETE FROM pending_sync_records").Error)
  196. require.NoError(t, db.Create(&model.PendingSyncRecord{
  197. UserId: 100, RemoteUserId: 200, RequestId: "req-part-1",
  198. Quota: 100, Status: model.PendingSyncStatusSettled, CreatedAt: now,
  199. }).Error)
  200. require.NoError(t, db.Create(&model.PendingSyncRecord{
  201. UserId: 101, RemoteUserId: 201, RequestId: "req-part-2",
  202. Quota: 200, Status: model.PendingSyncStatusSettled, CreatedAt: now + 1,
  203. }).Error)
  204. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  205. w.Header().Set("Content-Type", "application/json")
  206. json.NewEncoder(w).Encode(BatchDeductResponse{
  207. Success: true,
  208. Results: []DeductResult{
  209. {UserId: 200, Success: true, DeductedQuota: 100},
  210. {UserId: 201, Success: false, Error: "insufficient quota"},
  211. },
  212. })
  213. }))
  214. defer server.Close()
  215. manager := &SyncManager{client: NewSyncClient(server.URL, "test-key")}
  216. count := manager.RunBatchSync()
  217. assert.Equal(t, 1, count)
  218. var syncedCount, pendingCount int64
  219. db.Model(&model.PendingSyncRecord{}).Where("status = ?", model.PendingSyncStatusSynced).Count(&syncedCount)
  220. db.Model(&model.PendingSyncRecord{}).Where("status = ?", model.PendingSyncStatusSettled).Count(&pendingCount)
  221. assert.Equal(t, int64(1), syncedCount)
  222. assert.Equal(t, int64(1), pendingCount)
  223. })
  224. t.Run("ServerError", func(t *testing.T) {
  225. require.NoError(t, db.Exec("DELETE FROM pending_sync_records").Error)
  226. require.NoError(t, db.Create(&model.PendingSyncRecord{
  227. UserId: 100, RemoteUserId: 200, RequestId: "req-srv-err",
  228. Quota: 100, Status: model.PendingSyncStatusSettled, CreatedAt: now,
  229. }).Error)
  230. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  231. w.Header().Set("Content-Type", "application/json")
  232. w.WriteHeader(500)
  233. json.NewEncoder(w).Encode(BatchDeductResponse{Success: false, Error: "internal error"})
  234. }))
  235. defer server.Close()
  236. manager := &SyncManager{client: NewSyncClient(server.URL, "test-key")}
  237. count := manager.RunBatchSync()
  238. assert.Equal(t, 0, count)
  239. })
  240. }
  241. func TestRunBatchSync_RetryExhaustion(t *testing.T) {
  242. origDB := model.DB
  243. defer func() { model.DB = origDB }()
  244. db := setupTestDB(t)
  245. model.DB = db
  246. now := time.Now().Unix()
  247. require.NoError(t, db.Create(&model.PendingSyncRecord{
  248. UserId: 100, RemoteUserId: 200, RequestId: "req-retry-exh",
  249. Quota: 100, Status: model.PendingSyncStatusFailed, RetryCount: 2, CreatedAt: now,
  250. }).Error)
  251. settings := system_setting.GetRegionSyncSettings()
  252. origEnabled := settings.Enabled
  253. origBatchSize := settings.SyncBatchSize
  254. origMaxRetry := settings.MaxRetryCount
  255. defer func() {
  256. settings.Enabled = origEnabled
  257. settings.SyncBatchSize = origBatchSize
  258. settings.MaxRetryCount = origMaxRetry
  259. }()
  260. settings.Enabled = true
  261. settings.SyncBatchSize = 100
  262. settings.MaxRetryCount = 3
  263. manager := &SyncManager{client: NewSyncClient("http://127.0.0.1:0", "key")}
  264. count := manager.RunBatchSync()
  265. assert.Equal(t, 0, count)
  266. var record model.PendingSyncRecord
  267. require.NoError(t, db.Where("request_id = ?", "req-retry-exh").First(&record).Error)
  268. assert.Equal(t, model.PendingSyncStatusFailed, record.Status)
  269. assert.Equal(t, 3, record.RetryCount)
  270. }