mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-10-28 16:46:57 +08:00
* Add Responses WebSocket relay support * Fix Responses WebSocket relay protocol issues * Add status to Responses WebSocket errors * Fix Responses WebSocket local error handling * Use Responses error payload for control write failures * Fix WebSocket subprotocol auth handling * fix: address responses websocket CR feedback * fix: truncate responses websocket relay errors * fix: preserve websocket error message * test: cover responses usage output fallback * feat: close active websockets for disabled channels * chore: address websocket manager review feedback * fix: adapt responses websocket relay after rebase * feat: add per-channel Responses WebSocket switch * fix: address responses websocket relay review findings * fix: share Responses WebSocket billing with HTTP and retain existing rate limits
112 lines
5.2 KiB
Go
112 lines
5.2 KiB
Go
package controller
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/QuantumNous/new-api/common"
|
|
"github.com/QuantumNous/new-api/model"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestMultiKeyEnableRestoresOnlyExhaustedChannels(t *testing.T) {
|
|
previousDB, previousLogDB := model.DB, model.LOG_DB
|
|
previousType, previousLogType := common.MainDatabaseType(), common.LogDatabaseType()
|
|
previousMaster, previousCache, previousRedis, previousSQLite := common.IsMasterNode, common.MemoryCacheEnabled, common.RedisEnabled, common.SQLitePath
|
|
t.Cleanup(func() {
|
|
model.DB, model.LOG_DB = previousDB, previousLogDB
|
|
common.SetDatabaseTypes(previousType, previousLogType)
|
|
common.IsMasterNode, common.MemoryCacheEnabled, common.RedisEnabled, common.SQLitePath = previousMaster, previousCache, previousRedis, previousSQLite
|
|
})
|
|
t.Setenv("SQL_DSN", os.Getenv("TEST_CHANNEL_SQL_DSN"))
|
|
t.Setenv("LOG_SQL_DSN", "")
|
|
common.IsMasterNode, common.MemoryCacheEnabled, common.RedisEnabled = false, false, false
|
|
common.SQLitePath = filepath.Join(t.TempDir(), "channel.db")
|
|
require.NoError(t, model.InitDB())
|
|
database := model.DB
|
|
sqlDB, err := database.DB()
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { require.NoError(t, sqlDB.Close()) })
|
|
model.LOG_DB = database
|
|
common.SetLogDatabaseType(common.MainDatabaseType())
|
|
require.NoError(t, database.AutoMigrate(&model.Channel{}, &model.Ability{}, &model.User{}, &model.Log{}, &model.AuditLog{}))
|
|
root := &model.User{Username: "multi-key-review-root", Role: common.RoleRootUser, Status: common.UserStatusEnabled}
|
|
require.NoError(t, database.Create(root).Error)
|
|
t.Cleanup(func() { require.NoError(t, database.Unscoped().Delete(root).Error) })
|
|
versionQuery := "SELECT VERSION()"
|
|
if common.UsingMainDatabase(common.DatabaseTypeSQLite) {
|
|
versionQuery = "SELECT sqlite_version()"
|
|
}
|
|
var version string
|
|
require.NoError(t, database.Raw(versionQuery).Scan(&version).Error)
|
|
t.Logf("database=%s version=%s", common.MainDatabaseType(), version)
|
|
|
|
for _, cacheEnabled := range []bool{false, true} {
|
|
for _, action := range []string{"enable_key", "enable_all_keys"} {
|
|
for _, tc := range []struct {
|
|
name string
|
|
initialStatus int
|
|
manualOverride string
|
|
wantStatus int
|
|
}{
|
|
{name: "key exhaustion restores", initialStatus: common.ChannelStatusEnabled, wantStatus: common.ChannelStatusEnabled},
|
|
{name: "manual disable is preserved", initialStatus: common.ChannelStatusManuallyDisabled, wantStatus: common.ChannelStatusManuallyDisabled},
|
|
{name: "manual disable after exhaustion is preserved", initialStatus: common.ChannelStatusEnabled, manualOverride: "status", wantStatus: common.ChannelStatusManuallyDisabled},
|
|
{name: "tag disable after exhaustion is preserved", initialStatus: common.ChannelStatusEnabled, manualOverride: "tag", wantStatus: common.ChannelStatusManuallyDisabled},
|
|
} {
|
|
t.Run(fmt.Sprintf("cache=%t/%s/%s", cacheEnabled, action, tc.name), func(t *testing.T) {
|
|
common.MemoryCacheEnabled = cacheEnabled
|
|
tag := t.Name()
|
|
channel := &model.Channel{Name: t.Name(), Type: 1, Key: "key-one\nkey-two", Status: tc.initialStatus, Models: "test-model", Group: "default", Tag: &tag,
|
|
ChannelInfo: model.ChannelInfo{IsMultiKey: true, MultiKeySize: 2, MultiKeyStatusList: map[int]int{1: common.ChannelStatusManuallyDisabled}},
|
|
}
|
|
require.NoError(t, channel.Insert())
|
|
t.Cleanup(func() {
|
|
require.NoError(t, channel.Delete())
|
|
model.InitChannelCache()
|
|
})
|
|
for _, operation := range []string{"disable_key", action} {
|
|
if operation == action {
|
|
if tc.manualOverride == "status" {
|
|
model.UpdateChannelStatus(channel.Id, "", common.ChannelStatusManuallyDisabled, "manual operation")
|
|
} else if tc.manualOverride == "tag" {
|
|
require.NoError(t, model.DisableChannelByTag(tag))
|
|
}
|
|
}
|
|
payload, err := common.Marshal(MultiKeyManageRequest{ChannelId: channel.Id, Action: operation, KeyIndex: common.GetPointer(0)})
|
|
require.NoError(t, err)
|
|
recorder := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(recorder)
|
|
c.Set("id", root.Id)
|
|
c.Set("role", common.RoleRootUser)
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/api/channel/multi_key", bytes.NewReader(payload))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
ManageMultiKeys(c)
|
|
var result struct {
|
|
Success bool `json:"success"`
|
|
}
|
|
require.NoError(t, common.Unmarshal(recorder.Body.Bytes(), &result))
|
|
require.True(t, result.Success, recorder.Body.String())
|
|
}
|
|
loaded, err := model.GetChannelById(channel.Id, true)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tc.wantStatus, loaded.Status)
|
|
assert.NotContains(t, loaded.ChannelInfo.MultiKeyStatusList, 0)
|
|
assert.NotContains(t, loaded.ChannelInfo.MultiKeyDisabledReason, 0)
|
|
assert.NotContains(t, loaded.ChannelInfo.MultiKeyDisabledTime, 0)
|
|
var ability model.Ability
|
|
require.NoError(t, database.Where("channel_id = ?", channel.Id).First(&ability).Error)
|
|
assert.Equal(t, tc.wantStatus == common.ChannelStatusEnabled, ability.Enabled)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|