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.
 
 
 

223 lines
6.5 KiB

  1. package service
  2. import (
  3. "net/http"
  4. "net/http/httptest"
  5. "os"
  6. "strings"
  7. "testing"
  8. "github.com/QuantumNous/new-api/common"
  9. "github.com/QuantumNous/new-api/constant"
  10. "github.com/QuantumNous/new-api/model"
  11. "github.com/QuantumNous/new-api/setting"
  12. "github.com/gin-gonic/gin"
  13. "github.com/stretchr/testify/assert"
  14. "github.com/stretchr/testify/require"
  15. "gorm.io/gorm"
  16. "gorm.io/gorm/logger"
  17. )
  18. func buildRetryContext(t *testing.T) *gin.Context {
  19. t.Helper()
  20. gin.SetMode(gin.TestMode)
  21. w := httptest.NewRecorder()
  22. c, _ := gin.CreateTestContext(w)
  23. req, err := http.NewRequest(http.MethodPost, "/v1/chat/completions", nil)
  24. require.NoError(t, err)
  25. c.Request = req
  26. return c
  27. }
  28. func setupServiceChannelSelectDB(t *testing.T) *gorm.DB {
  29. t.Helper()
  30. oldDB := model.DB
  31. oldSQLitePath := common.SQLitePath
  32. oldMemoryCacheEnabled := common.MemoryCacheEnabled
  33. oldIsMasterNode := common.IsMasterNode
  34. oldUsingSQLite := common.UsingSQLite
  35. oldUsingMySQL := common.UsingMySQL
  36. oldUsingPostgreSQL := common.UsingPostgreSQL
  37. oldSQLDSN, hadSQLDSN := os.LookupEnv("SQL_DSN")
  38. common.SQLitePath = "file:" + strings.NewReplacer("/", "_", " ", "_").Replace(t.Name()) + "?mode=memory&cache=shared"
  39. common.MemoryCacheEnabled = false
  40. common.IsMasterNode = false
  41. common.UsingSQLite = false
  42. common.UsingMySQL = false
  43. common.UsingPostgreSQL = false
  44. require.NoError(t, os.Setenv("SQL_DSN", "local"))
  45. require.NoError(t, model.InitDB())
  46. model.DB = model.DB.Session(&gorm.Session{Logger: logger.Default.LogMode(logger.Silent)})
  47. db := model.DB
  48. sqlDB, err := db.DB()
  49. require.NoError(t, err)
  50. sqlDB.SetMaxOpenConns(1)
  51. require.NoError(t, db.AutoMigrate(&model.Channel{}, &model.Ability{}))
  52. t.Cleanup(func() {
  53. _ = sqlDB.Close()
  54. model.DB = oldDB
  55. common.SQLitePath = oldSQLitePath
  56. common.MemoryCacheEnabled = oldMemoryCacheEnabled
  57. common.IsMasterNode = oldIsMasterNode
  58. common.UsingSQLite = oldUsingSQLite
  59. common.UsingMySQL = oldUsingMySQL
  60. common.UsingPostgreSQL = oldUsingPostgreSQL
  61. if hadSQLDSN {
  62. _ = os.Setenv("SQL_DSN", oldSQLDSN)
  63. } else {
  64. _ = os.Unsetenv("SQL_DSN")
  65. }
  66. })
  67. return db
  68. }
  69. func createServiceChannelSelectChannel(t *testing.T, db *gorm.DB, id int, channelType int, group string, modelName string) {
  70. t.Helper()
  71. priority := int64(10)
  72. weight := uint(10)
  73. autoBan := 1
  74. require.NoError(t, db.Create(&model.Channel{
  75. Id: id,
  76. Type: channelType,
  77. Key: "key",
  78. Status: common.ChannelStatusEnabled,
  79. Name: "channel",
  80. Group: group,
  81. Models: modelName,
  82. Priority: &priority,
  83. Weight: &weight,
  84. AutoBan: &autoBan,
  85. }).Error)
  86. require.NoError(t, db.Create(&model.Ability{
  87. Group: group,
  88. Model: modelName,
  89. ChannelId: id,
  90. Enabled: true,
  91. Priority: &priority,
  92. Weight: 10,
  93. }).Error)
  94. }
  95. // --- RetryParam tests ---
  96. func TestRetryParam_IncreaseRetry(t *testing.T) {
  97. p := &RetryParam{}
  98. assert.Equal(t, 0, p.GetRetry())
  99. p.IncreaseRetry()
  100. assert.Equal(t, 1, p.GetRetry())
  101. p.IncreaseRetry()
  102. assert.Equal(t, 2, p.GetRetry())
  103. }
  104. func TestRetryParam_ResetRetryNextTry(t *testing.T) {
  105. p := &RetryParam{}
  106. p.IncreaseRetry()
  107. assert.Equal(t, 1, p.GetRetry())
  108. // ResetRetryNextTry should cause next IncreaseRetry to be a no-op
  109. p.ResetRetryNextTry()
  110. p.IncreaseRetry()
  111. assert.Equal(t, 1, p.GetRetry()) // still 1
  112. p.IncreaseRetry()
  113. assert.Equal(t, 2, p.GetRetry()) // now increases
  114. }
  115. func TestRetryParam_SetRetry(t *testing.T) {
  116. p := &RetryParam{}
  117. p.SetRetry(5)
  118. assert.Equal(t, 5, p.GetRetry())
  119. }
  120. // --- Auto group selection (no DB, tests with no channels available) ---
  121. func TestCacheGetRandomSatisfiedChannel_AutoGroup_NoAutoGroups(t *testing.T) {
  122. require.NoError(t, setting.UpdateAutoGroupsByJsonString(`[]`))
  123. t.Cleanup(func() {
  124. _ = setting.UpdateAutoGroupsByJsonString(`["default"]`)
  125. _ = setting.UpdateUserUsableGroupsByJSONString(`{"default":"默认分组","vip":"vip分组"}`)
  126. })
  127. c := buildRetryContext(t)
  128. p := &RetryParam{
  129. Ctx: c,
  130. TokenGroup: "auto",
  131. ModelName: "gpt-4",
  132. }
  133. ch, group, err := CacheGetRandomSatisfiedChannel(p)
  134. assert.Nil(t, ch)
  135. assert.Error(t, err)
  136. assert.Equal(t, "auto", group)
  137. }
  138. func TestCacheGetRandomSatisfiedChannelFiltersAllowedChannelTypes(t *testing.T) {
  139. db := setupServiceChannelSelectDB(t)
  140. createServiceChannelSelectChannel(t, db, 101, constant.ChannelTypeDoubaoVideoCompatibleAiping, "default", "Doubao-Seedance-2.0")
  141. createServiceChannelSelectChannel(t, db, 102, constant.ChannelTypeOpenAI, "default", "Doubao-Seedance-2.0")
  142. c := buildRetryContext(t)
  143. channel, group, err := CacheGetRandomSatisfiedChannel(&RetryParam{
  144. Ctx: c,
  145. TokenGroup: "default",
  146. ModelName: "Doubao-Seedance-2.0",
  147. Retry: common.GetPointer(0),
  148. AllowedChannelTypes: []int{constant.ChannelTypeDoubaoVideoCompatibleAiping, constant.ChannelTypeDoubaoVideoCompatibleTianyiYun},
  149. })
  150. require.NoError(t, err)
  151. require.NotNil(t, channel)
  152. assert.Equal(t, "default", group)
  153. assert.Equal(t, 101, channel.Id)
  154. }
  155. // --- GetUserAutoGroup tests ---
  156. func TestGetUserAutoGroup_Basic(t *testing.T) {
  157. require.NoError(t, setting.UpdateAutoGroupsByJsonString(`["default","vip"]`))
  158. require.NoError(t, setting.UpdateUserUsableGroupsByJSONString(`{"default":"默认分组","vip":"vip分组"}`))
  159. t.Cleanup(func() {
  160. _ = setting.UpdateAutoGroupsByJsonString(`["default"]`)
  161. _ = setting.UpdateUserUsableGroupsByJSONString(`{"default":"默认分组","vip":"vip分组"}`)
  162. })
  163. // default user can use both groups
  164. autoGroups := GetUserAutoGroup("default")
  165. assert.Equal(t, []string{"default", "vip"}, autoGroups)
  166. }
  167. func TestGetUserAutoGroup_VipUserWithExtraGroups(t *testing.T) {
  168. require.NoError(t, setting.UpdateAutoGroupsByJsonString(`["default","vip","premium"]`))
  169. require.NoError(t, setting.UpdateUserUsableGroupsByJSONString(`{"default":"默认分组","vip":"vip分组","premium":"高级分组"}`))
  170. t.Cleanup(func() {
  171. _ = setting.UpdateAutoGroupsByJsonString(`["default"]`)
  172. _ = setting.UpdateUserUsableGroupsByJSONString(`{"default":"默认分组","vip":"vip分组"}`)
  173. })
  174. autoGroups := GetUserAutoGroup("vip")
  175. assert.Equal(t, []string{"default", "vip", "premium"}, autoGroups)
  176. }
  177. // --- GetUserGroupRatio tests ---
  178. func TestGetUserGroupRatio_SpecialRatio(t *testing.T) {
  179. // When user has a special ratio for a group, it should be used
  180. // The default groupGroupRatio has vip->edit_this = 0.9
  181. ratio := GetUserGroupRatio("vip", "edit_this")
  182. assert.Equal(t, 0.9, ratio)
  183. }
  184. func TestGetUserGroupRatio_FallbackToGroupRatio(t *testing.T) {
  185. // When no special ratio exists, fall back to group ratio
  186. ratio := GetUserGroupRatio("default", "default")
  187. assert.Equal(t, 1.0, ratio) // default group has ratio 1
  188. }