Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 

231 строка
7.2 KiB

  1. package relay
  2. import (
  3. "net/http"
  4. "net/http/httptest"
  5. "strings"
  6. "testing"
  7. "github.com/QuantumNous/new-api/common"
  8. "github.com/QuantumNous/new-api/constant"
  9. "github.com/QuantumNous/new-api/model"
  10. relaycommon "github.com/QuantumNous/new-api/relay/common"
  11. "github.com/QuantumNous/new-api/types"
  12. "github.com/gin-gonic/gin"
  13. "github.com/glebarez/sqlite"
  14. "github.com/stretchr/testify/assert"
  15. "github.com/stretchr/testify/require"
  16. "gorm.io/gorm"
  17. )
  18. func setupRelayTaskTestDB(t *testing.T) {
  19. t.Helper()
  20. db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
  21. require.NoError(t, err)
  22. require.NoError(t, db.AutoMigrate(&model.Task{}, &model.Channel{}))
  23. sqlDB, err := db.DB()
  24. require.NoError(t, err)
  25. sqlDB.SetMaxOpenConns(1)
  26. origDB := model.DB
  27. origUsingSQLite := common.UsingSQLite
  28. origRedisEnabled := common.RedisEnabled
  29. model.DB = db
  30. common.UsingSQLite = true
  31. common.RedisEnabled = false
  32. t.Cleanup(func() {
  33. model.DB = origDB
  34. common.UsingSQLite = origUsingSQLite
  35. common.RedisEnabled = origRedisEnabled
  36. require.NoError(t, sqlDB.Close())
  37. })
  38. }
  39. func TestResolveOriginTask_RestoresMatrixOriginPricing(t *testing.T) {
  40. setupRelayTaskTestDB(t)
  41. require.NoError(t, model.DB.Create(&model.Channel{
  42. Id: 11,
  43. Type: constant.ChannelTypeMiniMax,
  44. Key: "test-key",
  45. Status: common.ChannelStatusEnabled,
  46. Name: "hailuo",
  47. Group: "default",
  48. Models: "matrix-video",
  49. }).Error)
  50. require.NoError(t, model.DB.Create(&model.Task{
  51. TaskID: "task_origin",
  52. UserId: 7,
  53. ChannelId: 11,
  54. Properties: model.Properties{
  55. OriginModelName: "matrix-video",
  56. },
  57. PrivateData: model.TaskPrivateData{
  58. BillingContext: &model.TaskBillingContext{
  59. BillingMode: types.BillingModeMatrix,
  60. BillingUnit: types.BillingUnitPer1MTokens,
  61. TokenUnitPriceUSD: 0.5,
  62. PricingSnapshot: map[string]any{
  63. "resolution": "720P",
  64. "price": 0.5,
  65. "billing_unit": types.BillingUnitPer1MTokens,
  66. "pricing_mode": string(types.PricingModeMatched),
  67. },
  68. PerCallBilling: false,
  69. },
  70. },
  71. }).Error)
  72. c := newRelayTaskRemixContext(t, "task_origin")
  73. info := &relaycommon.RelayInfo{UserId: 7, TaskRelayInfo: &relaycommon.TaskRelayInfo{}}
  74. taskErr := ResolveOriginTask(c, info)
  75. require.Nil(t, taskErr)
  76. require.NotNil(t, info.OriginPricing)
  77. require.NotNil(t, info.LockedChannel)
  78. assert.Equal(t, constant.TaskActionRemix, info.Action)
  79. assert.Equal(t, "matrix-video", info.OriginModelName)
  80. assert.Equal(t, types.BillingModeMatrix, info.OriginPricing.BillingMode)
  81. assert.Equal(t, types.BillingUnitPer1MTokens, info.OriginPricing.BillingUnit)
  82. assert.Equal(t, 0.5, info.OriginPricing.TokenUnitPriceUSD)
  83. assert.Equal(t, "720P", info.OriginPricing.Snapshot["resolution"])
  84. lockedChannel, ok := info.LockedChannel.(*model.Channel)
  85. require.True(t, ok)
  86. assert.Equal(t, 11, lockedChannel.Id)
  87. }
  88. func TestResolveOriginTask_RestoresLegacyOriginPricing(t *testing.T) {
  89. setupRelayTaskTestDB(t)
  90. require.NoError(t, model.DB.Create(&model.Channel{
  91. Id: 12,
  92. Type: constant.ChannelTypeOpenAI,
  93. Key: "test-key",
  94. Status: common.ChannelStatusEnabled,
  95. Name: "sora",
  96. Group: "default",
  97. Models: "legacy-video",
  98. }).Error)
  99. require.NoError(t, model.DB.Create(&model.Task{
  100. TaskID: "task_legacy",
  101. UserId: 7,
  102. ChannelId: 12,
  103. Properties: model.Properties{
  104. OriginModelName: "legacy-video",
  105. },
  106. PrivateData: model.TaskPrivateData{
  107. BillingContext: &model.TaskBillingContext{
  108. ModelPrice: 0.2,
  109. OtherRatios: map[string]float64{"seconds": 10, "size": 1.666667},
  110. PerCallBilling: true,
  111. },
  112. },
  113. }).Error)
  114. c := newRelayTaskRemixContext(t, "task_legacy")
  115. info := &relaycommon.RelayInfo{UserId: 7, TaskRelayInfo: &relaycommon.TaskRelayInfo{}}
  116. taskErr := ResolveOriginTask(c, info)
  117. require.Nil(t, taskErr)
  118. require.NotNil(t, info.OriginPricing)
  119. require.NotNil(t, info.LockedChannel)
  120. assert.Equal(t, "legacy-video", info.OriginModelName)
  121. assert.Equal(t, 0.2, info.OriginPricing.PriceUSD)
  122. assert.Equal(t, map[string]float64{"seconds": 10, "size": 1.666667}, info.OriginPricing.OtherRatios)
  123. assert.True(t, info.OriginPricing.PerCallBilling)
  124. lockedChannel, ok := info.LockedChannel.(*model.Channel)
  125. require.True(t, ok)
  126. assert.Equal(t, 12, lockedChannel.Id)
  127. }
  128. func TestBuildUpstreamRequestSnapshotRedactsDataURIsAndKeepsDebugFields(t *testing.T) {
  129. raw := []byte(`{
  130. "model":"doubao-seedance-2-0-260128",
  131. "content":[
  132. {"type":"text","text":"prompt"},
  133. {"type":"image_url","image_url":{"url":"data:image/png;base64,QUJDREVGRw=="}},
  134. {"type":"video_url","video_url":{"url":"asset://video-1"}},
  135. {"type":"audio_url","audio_url":{"url":"https://example.test/audio.wav"}}
  136. ]
  137. }`)
  138. snapshot := buildUpstreamRequestSnapshot(raw)
  139. require.NotNil(t, snapshot)
  140. require.Equal(t, len(raw), snapshot.RawBytes)
  141. require.NotEmpty(t, snapshot.SHA256)
  142. require.True(t, snapshot.Redacted)
  143. require.False(t, snapshot.Truncated)
  144. data, err := common.Marshal(snapshot)
  145. require.NoError(t, err)
  146. jsonBody := string(data)
  147. require.Contains(t, jsonBody, `"model":"doubao-seedance-2-0-260128"`)
  148. require.Contains(t, jsonBody, `"asset://video-1"`)
  149. require.Contains(t, jsonBody, `"https://example.test/audio.wav"`)
  150. require.Contains(t, jsonBody, `"kind":"data_uri"`)
  151. require.Contains(t, jsonBody, `"media_type":"image/png"`)
  152. require.NotContains(t, jsonBody, "QUJDREVGRw==")
  153. require.Equal(t, len(data), snapshot.StoredBytes)
  154. }
  155. func TestBuildUpstreamRequestSnapshotTruncatesOversizedSnapshot(t *testing.T) {
  156. raw := []byte(`{"model":"m","prompt":"` + strings.Repeat("x", maxUpstreamRequestSnapshotBytes+1024) + `"}`)
  157. snapshot := buildUpstreamRequestSnapshot(raw)
  158. require.NotNil(t, snapshot)
  159. require.True(t, snapshot.Truncated)
  160. require.NotEmpty(t, snapshot.SHA256)
  161. data, err := common.Marshal(snapshot)
  162. require.NoError(t, err)
  163. require.LessOrEqual(t, len(data), maxUpstreamRequestSnapshotBytes)
  164. require.Equal(t, len(data), snapshot.StoredBytes)
  165. }
  166. func TestTaskModel2DtoDoesNotExposeUpstreamRequestSnapshot(t *testing.T) {
  167. task := &model.Task{
  168. TaskID: "task_public",
  169. PrivateData: model.TaskPrivateData{
  170. UpstreamRequest: &model.TaskUpstreamRequestSnapshot{
  171. Body: map[string]any{"secret": "hidden"},
  172. },
  173. },
  174. }
  175. dtoTask := TaskModel2Dto(task)
  176. data, err := common.Marshal(dtoTask)
  177. require.NoError(t, err)
  178. require.NotContains(t, string(data), "upstream_request")
  179. require.NotContains(t, string(data), "hidden")
  180. }
  181. func TestTaskModel2DtoSanitizesAipingInternalIDFromData(t *testing.T) {
  182. task := &model.Task{
  183. TaskID: "task_public",
  184. Data: []byte(`{
  185. "id":"cgt-upstream",
  186. "aiping_id":"b7ee658f-acac-433d-897d-5c9ed55ef402",
  187. "status":"succeeded"
  188. }`),
  189. }
  190. dtoTask := TaskModel2Dto(task)
  191. data, err := common.Marshal(dtoTask)
  192. require.NoError(t, err)
  193. require.NotContains(t, string(data), "aiping_id")
  194. require.NotContains(t, string(data), "b7ee658f-acac-433d-897d-5c9ed55ef402")
  195. require.Contains(t, string(data), `"id":"task_public"`)
  196. }
  197. func newRelayTaskRemixContext(t *testing.T, originTaskID string) *gin.Context {
  198. t.Helper()
  199. gin.SetMode(gin.TestMode)
  200. w := httptest.NewRecorder()
  201. c, _ := gin.CreateTestContext(w)
  202. c.Request = httptest.NewRequest(http.MethodPost, "/v1/videos/"+originTaskID+"/remix", nil)
  203. c.Params = gin.Params{{Key: "video_id", Value: originTaskID}}
  204. return c
  205. }