25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 

241 satır
6.3 KiB

  1. package model
  2. import (
  3. "encoding/json"
  4. "os"
  5. "sync"
  6. "testing"
  7. "time"
  8. "github.com/QuantumNous/new-api/common"
  9. "github.com/glebarez/sqlite"
  10. "github.com/stretchr/testify/assert"
  11. "github.com/stretchr/testify/require"
  12. "gorm.io/gorm"
  13. )
  14. func TestMain(m *testing.M) {
  15. db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
  16. if err != nil {
  17. panic("failed to open test db: " + err.Error())
  18. }
  19. DB = db
  20. LOG_DB = db
  21. common.UsingSQLite = true
  22. common.RedisEnabled = false
  23. common.BatchUpdateEnabled = false
  24. common.LogConsumeEnabled = true
  25. sqlDB, err := db.DB()
  26. if err != nil {
  27. panic("failed to get sql.DB: " + err.Error())
  28. }
  29. sqlDB.SetMaxOpenConns(1)
  30. if err := db.AutoMigrate(&Task{}, &User{}, &Token{}, &Log{}, &Channel{}); err != nil {
  31. panic("failed to migrate: " + err.Error())
  32. }
  33. os.Exit(m.Run())
  34. }
  35. func truncateTables(t *testing.T) {
  36. t.Helper()
  37. t.Cleanup(func() {
  38. DB.Exec("DELETE FROM tasks")
  39. DB.Exec("DELETE FROM users")
  40. DB.Exec("DELETE FROM tokens")
  41. DB.Exec("DELETE FROM logs")
  42. DB.Exec("DELETE FROM channels")
  43. })
  44. }
  45. func insertTask(t *testing.T, task *Task) {
  46. t.Helper()
  47. task.CreatedAt = time.Now().Unix()
  48. task.UpdatedAt = time.Now().Unix()
  49. require.NoError(t, DB.Create(task).Error)
  50. }
  51. // ---------------------------------------------------------------------------
  52. // Snapshot / Equal — pure logic tests (no DB)
  53. // ---------------------------------------------------------------------------
  54. func TestSnapshotEqual_Same(t *testing.T) {
  55. s := taskSnapshot{
  56. Status: TaskStatusInProgress,
  57. Progress: "50%",
  58. StartTime: 1000,
  59. FinishTime: 0,
  60. FailReason: "",
  61. ResultURL: "",
  62. Data: json.RawMessage(`{"key":"value"}`),
  63. }
  64. assert.True(t, s.Equal(s))
  65. }
  66. func TestSnapshotEqual_DifferentStatus(t *testing.T) {
  67. a := taskSnapshot{Status: TaskStatusInProgress, Data: json.RawMessage(`{}`)}
  68. b := taskSnapshot{Status: TaskStatusSuccess, Data: json.RawMessage(`{}`)}
  69. assert.False(t, a.Equal(b))
  70. }
  71. func TestSnapshotEqual_DifferentProgress(t *testing.T) {
  72. a := taskSnapshot{Status: TaskStatusInProgress, Progress: "30%", Data: json.RawMessage(`{}`)}
  73. b := taskSnapshot{Status: TaskStatusInProgress, Progress: "60%", Data: json.RawMessage(`{}`)}
  74. assert.False(t, a.Equal(b))
  75. }
  76. func TestSnapshotEqual_DifferentData(t *testing.T) {
  77. a := taskSnapshot{Status: TaskStatusInProgress, Data: json.RawMessage(`{"a":1}`)}
  78. b := taskSnapshot{Status: TaskStatusInProgress, Data: json.RawMessage(`{"a":2}`)}
  79. assert.False(t, a.Equal(b))
  80. }
  81. func TestSnapshotEqual_NilVsEmpty(t *testing.T) {
  82. a := taskSnapshot{Status: TaskStatusInProgress, Data: nil}
  83. b := taskSnapshot{Status: TaskStatusInProgress, Data: json.RawMessage{}}
  84. // bytes.Equal(nil, []byte{}) == true
  85. assert.True(t, a.Equal(b))
  86. }
  87. func TestSnapshot_Roundtrip(t *testing.T) {
  88. task := &Task{
  89. Status: TaskStatusInProgress,
  90. Progress: "42%",
  91. StartTime: 1234,
  92. FinishTime: 5678,
  93. FailReason: "timeout",
  94. PrivateData: TaskPrivateData{
  95. ResultURL: "https://example.com/result.mp4",
  96. },
  97. Data: json.RawMessage(`{"model":"test-model"}`),
  98. }
  99. snap := task.Snapshot()
  100. assert.Equal(t, task.Status, snap.Status)
  101. assert.Equal(t, task.Progress, snap.Progress)
  102. assert.Equal(t, task.StartTime, snap.StartTime)
  103. assert.Equal(t, task.FinishTime, snap.FinishTime)
  104. assert.Equal(t, task.FailReason, snap.FailReason)
  105. assert.Equal(t, task.PrivateData.ResultURL, snap.ResultURL)
  106. assert.JSONEq(t, string(task.Data), string(snap.Data))
  107. }
  108. func TestTaskPrivateDataStoresUpstreamRequestSnapshot(t *testing.T) {
  109. privateData := TaskPrivateData{
  110. UpstreamRequest: &TaskUpstreamRequestSnapshot{
  111. Body: map[string]any{"model": "doubao-seedance-2-0-260128"},
  112. RawBytes: 42,
  113. StoredBytes: 128,
  114. Redacted: true,
  115. SHA256: "abc123",
  116. },
  117. }
  118. value, err := privateData.Value()
  119. require.NoError(t, err)
  120. var restored TaskPrivateData
  121. require.NoError(t, restored.Scan(value))
  122. require.NotNil(t, restored.UpstreamRequest)
  123. assert.Equal(t, 42, restored.UpstreamRequest.RawBytes)
  124. assert.Equal(t, 128, restored.UpstreamRequest.StoredBytes)
  125. assert.True(t, restored.UpstreamRequest.Redacted)
  126. assert.Equal(t, "abc123", restored.UpstreamRequest.SHA256)
  127. }
  128. // ---------------------------------------------------------------------------
  129. // UpdateWithStatus CAS — DB integration tests
  130. // ---------------------------------------------------------------------------
  131. func TestUpdateWithStatus_Win(t *testing.T) {
  132. truncateTables(t)
  133. task := &Task{
  134. TaskID: "task_cas_win",
  135. Status: TaskStatusInProgress,
  136. Progress: "50%",
  137. Data: json.RawMessage(`{}`),
  138. }
  139. insertTask(t, task)
  140. task.Status = TaskStatusSuccess
  141. task.Progress = "100%"
  142. won, err := task.UpdateWithStatus(TaskStatusInProgress)
  143. require.NoError(t, err)
  144. assert.True(t, won)
  145. var reloaded Task
  146. require.NoError(t, DB.First(&reloaded, task.ID).Error)
  147. assert.EqualValues(t, TaskStatusSuccess, reloaded.Status)
  148. assert.Equal(t, "100%", reloaded.Progress)
  149. }
  150. func TestUpdateWithStatus_Lose(t *testing.T) {
  151. truncateTables(t)
  152. task := &Task{
  153. TaskID: "task_cas_lose",
  154. Status: TaskStatusFailure,
  155. Data: json.RawMessage(`{}`),
  156. }
  157. insertTask(t, task)
  158. task.Status = TaskStatusSuccess
  159. won, err := task.UpdateWithStatus(TaskStatusInProgress) // wrong fromStatus
  160. require.NoError(t, err)
  161. assert.False(t, won)
  162. var reloaded Task
  163. require.NoError(t, DB.First(&reloaded, task.ID).Error)
  164. assert.EqualValues(t, TaskStatusFailure, reloaded.Status) // unchanged
  165. }
  166. func TestUpdateWithStatus_ConcurrentWinner(t *testing.T) {
  167. truncateTables(t)
  168. task := &Task{
  169. TaskID: "task_cas_race",
  170. Status: TaskStatusInProgress,
  171. Quota: 1000,
  172. Data: json.RawMessage(`{}`),
  173. }
  174. insertTask(t, task)
  175. const goroutines = 5
  176. wins := make([]bool, goroutines)
  177. var wg sync.WaitGroup
  178. wg.Add(goroutines)
  179. for i := 0; i < goroutines; i++ {
  180. go func(idx int) {
  181. defer wg.Done()
  182. t := &Task{}
  183. *t = Task{
  184. ID: task.ID,
  185. TaskID: task.TaskID,
  186. Status: TaskStatusSuccess,
  187. Progress: "100%",
  188. Quota: task.Quota,
  189. Data: json.RawMessage(`{}`),
  190. }
  191. t.CreatedAt = task.CreatedAt
  192. t.UpdatedAt = time.Now().Unix()
  193. won, err := t.UpdateWithStatus(TaskStatusInProgress)
  194. if err == nil {
  195. wins[idx] = won
  196. }
  197. }(i)
  198. }
  199. wg.Wait()
  200. winCount := 0
  201. for _, w := range wins {
  202. if w {
  203. winCount++
  204. }
  205. }
  206. assert.Equal(t, 1, winCount, "exactly one goroutine should win the CAS")
  207. }