|
- package service
-
- import (
- "net/http"
- "net/http/httptest"
- "os"
- "strings"
- "testing"
-
- "github.com/QuantumNous/new-api/common"
- "github.com/QuantumNous/new-api/constant"
- "github.com/QuantumNous/new-api/model"
- "github.com/QuantumNous/new-api/setting"
- "github.com/gin-gonic/gin"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "gorm.io/gorm"
- "gorm.io/gorm/logger"
- )
-
- func buildRetryContext(t *testing.T) *gin.Context {
- t.Helper()
- gin.SetMode(gin.TestMode)
- w := httptest.NewRecorder()
- c, _ := gin.CreateTestContext(w)
- req, err := http.NewRequest(http.MethodPost, "/v1/chat/completions", nil)
- require.NoError(t, err)
- c.Request = req
- return c
- }
-
- func setupServiceChannelSelectDB(t *testing.T) *gorm.DB {
- t.Helper()
-
- oldDB := model.DB
- oldSQLitePath := common.SQLitePath
- oldMemoryCacheEnabled := common.MemoryCacheEnabled
- oldIsMasterNode := common.IsMasterNode
- oldUsingSQLite := common.UsingSQLite
- oldUsingMySQL := common.UsingMySQL
- oldUsingPostgreSQL := common.UsingPostgreSQL
- oldSQLDSN, hadSQLDSN := os.LookupEnv("SQL_DSN")
-
- common.SQLitePath = "file:" + strings.NewReplacer("/", "_", " ", "_").Replace(t.Name()) + "?mode=memory&cache=shared"
- common.MemoryCacheEnabled = false
- common.IsMasterNode = false
- common.UsingSQLite = false
- common.UsingMySQL = false
- common.UsingPostgreSQL = false
- require.NoError(t, os.Setenv("SQL_DSN", "local"))
- require.NoError(t, model.InitDB())
-
- model.DB = model.DB.Session(&gorm.Session{Logger: logger.Default.LogMode(logger.Silent)})
- db := model.DB
- sqlDB, err := db.DB()
- require.NoError(t, err)
- sqlDB.SetMaxOpenConns(1)
- require.NoError(t, db.AutoMigrate(&model.Channel{}, &model.Ability{}))
-
- t.Cleanup(func() {
- _ = sqlDB.Close()
- model.DB = oldDB
- common.SQLitePath = oldSQLitePath
- common.MemoryCacheEnabled = oldMemoryCacheEnabled
- common.IsMasterNode = oldIsMasterNode
- common.UsingSQLite = oldUsingSQLite
- common.UsingMySQL = oldUsingMySQL
- common.UsingPostgreSQL = oldUsingPostgreSQL
- if hadSQLDSN {
- _ = os.Setenv("SQL_DSN", oldSQLDSN)
- } else {
- _ = os.Unsetenv("SQL_DSN")
- }
- })
-
- return db
- }
-
- func createServiceChannelSelectChannel(t *testing.T, db *gorm.DB, id int, channelType int, group string, modelName string) {
- t.Helper()
-
- priority := int64(10)
- weight := uint(10)
- autoBan := 1
- require.NoError(t, db.Create(&model.Channel{
- Id: id,
- Type: channelType,
- Key: "key",
- Status: common.ChannelStatusEnabled,
- Name: "channel",
- Group: group,
- Models: modelName,
- Priority: &priority,
- Weight: &weight,
- AutoBan: &autoBan,
- }).Error)
- require.NoError(t, db.Create(&model.Ability{
- Group: group,
- Model: modelName,
- ChannelId: id,
- Enabled: true,
- Priority: &priority,
- Weight: 10,
- }).Error)
- }
-
- // --- RetryParam tests ---
-
- func TestRetryParam_IncreaseRetry(t *testing.T) {
- p := &RetryParam{}
- assert.Equal(t, 0, p.GetRetry())
-
- p.IncreaseRetry()
- assert.Equal(t, 1, p.GetRetry())
-
- p.IncreaseRetry()
- assert.Equal(t, 2, p.GetRetry())
- }
-
- func TestRetryParam_ResetRetryNextTry(t *testing.T) {
- p := &RetryParam{}
- p.IncreaseRetry()
- assert.Equal(t, 1, p.GetRetry())
-
- // ResetRetryNextTry should cause next IncreaseRetry to be a no-op
- p.ResetRetryNextTry()
- p.IncreaseRetry()
- assert.Equal(t, 1, p.GetRetry()) // still 1
-
- p.IncreaseRetry()
- assert.Equal(t, 2, p.GetRetry()) // now increases
- }
-
- func TestRetryParam_SetRetry(t *testing.T) {
- p := &RetryParam{}
- p.SetRetry(5)
- assert.Equal(t, 5, p.GetRetry())
- }
-
- // --- Auto group selection (no DB, tests with no channels available) ---
-
- func TestCacheGetRandomSatisfiedChannel_AutoGroup_NoAutoGroups(t *testing.T) {
- require.NoError(t, setting.UpdateAutoGroupsByJsonString(`[]`))
- t.Cleanup(func() {
- _ = setting.UpdateAutoGroupsByJsonString(`["default"]`)
- _ = setting.UpdateUserUsableGroupsByJSONString(`{"default":"默认分组","vip":"vip分组"}`)
- })
-
- c := buildRetryContext(t)
- p := &RetryParam{
- Ctx: c,
- TokenGroup: "auto",
- ModelName: "gpt-4",
- }
-
- ch, group, err := CacheGetRandomSatisfiedChannel(p)
- assert.Nil(t, ch)
- assert.Error(t, err)
- assert.Equal(t, "auto", group)
- }
-
- func TestCacheGetRandomSatisfiedChannelFiltersAllowedChannelTypes(t *testing.T) {
- db := setupServiceChannelSelectDB(t)
- createServiceChannelSelectChannel(t, db, 101, constant.ChannelTypeDoubaoVideoCompatibleAiping, "default", "Doubao-Seedance-2.0")
- createServiceChannelSelectChannel(t, db, 102, constant.ChannelTypeOpenAI, "default", "Doubao-Seedance-2.0")
-
- c := buildRetryContext(t)
- channel, group, err := CacheGetRandomSatisfiedChannel(&RetryParam{
- Ctx: c,
- TokenGroup: "default",
- ModelName: "Doubao-Seedance-2.0",
- Retry: common.GetPointer(0),
- AllowedChannelTypes: []int{constant.ChannelTypeDoubaoVideoCompatibleAiping, constant.ChannelTypeDoubaoVideoCompatibleTianyiYun},
- })
-
- require.NoError(t, err)
- require.NotNil(t, channel)
- assert.Equal(t, "default", group)
- assert.Equal(t, 101, channel.Id)
- }
-
- // --- GetUserAutoGroup tests ---
-
- func TestGetUserAutoGroup_Basic(t *testing.T) {
- require.NoError(t, setting.UpdateAutoGroupsByJsonString(`["default","vip"]`))
- require.NoError(t, setting.UpdateUserUsableGroupsByJSONString(`{"default":"默认分组","vip":"vip分组"}`))
- t.Cleanup(func() {
- _ = setting.UpdateAutoGroupsByJsonString(`["default"]`)
- _ = setting.UpdateUserUsableGroupsByJSONString(`{"default":"默认分组","vip":"vip分组"}`)
- })
-
- // default user can use both groups
- autoGroups := GetUserAutoGroup("default")
- assert.Equal(t, []string{"default", "vip"}, autoGroups)
- }
-
- func TestGetUserAutoGroup_VipUserWithExtraGroups(t *testing.T) {
- require.NoError(t, setting.UpdateAutoGroupsByJsonString(`["default","vip","premium"]`))
- require.NoError(t, setting.UpdateUserUsableGroupsByJSONString(`{"default":"默认分组","vip":"vip分组","premium":"高级分组"}`))
- t.Cleanup(func() {
- _ = setting.UpdateAutoGroupsByJsonString(`["default"]`)
- _ = setting.UpdateUserUsableGroupsByJSONString(`{"default":"默认分组","vip":"vip分组"}`)
- })
-
- autoGroups := GetUserAutoGroup("vip")
- assert.Equal(t, []string{"default", "vip", "premium"}, autoGroups)
- }
-
- // --- GetUserGroupRatio tests ---
-
- func TestGetUserGroupRatio_SpecialRatio(t *testing.T) {
- // When user has a special ratio for a group, it should be used
- // The default groupGroupRatio has vip->edit_this = 0.9
- ratio := GetUserGroupRatio("vip", "edit_this")
- assert.Equal(t, 0.9, ratio)
- }
-
- func TestGetUserGroupRatio_FallbackToGroupRatio(t *testing.T) {
- // When no special ratio exists, fall back to group ratio
- ratio := GetUserGroupRatio("default", "default")
- assert.Equal(t, 1.0, ratio) // default group has ratio 1
- }
|