package model import ( "fmt" "testing" "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/constant" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/gorm" ) func setupChannelStatusTest(t *testing.T) { t.Helper() truncateTables(t) require.NoError(t, DB.Exec("DELETE FROM abilities").Error) require.NoError(t, DB.Exec("DELETE FROM channels").Error) memoryCacheEnabled := common.MemoryCacheEnabled common.MemoryCacheEnabled = false t.Cleanup(func() { common.MemoryCacheEnabled = memoryCacheEnabled }) } func TestUpdateChannelStatusPersistsMultiKeyState(t *testing.T) { setupChannelStatusTest(t) channel := Channel{ Name: "multi-key-status", Key: "key-a\nkey-b", Status: common.ChannelStatusEnabled, ChannelInfo: ChannelInfo{ IsMultiKey: true, MultiKeySize: 2, MultiKeyMode: constant.MultiKeyModePolling, MultiKeyPollingIndex: 1, }, } require.NoError(t, DB.Create(&channel).Error) changed := UpdateChannelStatus(channel.Id, "key-a", common.ChannelStatusAutoDisabled, "provider rejected key") require.True(t, changed) var stored Channel require.NoError(t, DB.First(&stored, channel.Id).Error) assert.Equal(t, common.ChannelStatusEnabled, stored.Status) assert.Equal(t, common.ChannelStatusAutoDisabled, stored.ChannelInfo.MultiKeyStatusList[0]) assert.Equal(t, "provider rejected key", stored.ChannelInfo.MultiKeyDisabledReason[0]) assert.NotZero(t, stored.ChannelInfo.MultiKeyDisabledTime[0]) assert.Equal(t, 1, stored.ChannelInfo.MultiKeyPollingIndex) } func TestSaveStatusStateFromSingleKeySnapshotPreservesUnownedColumns(t *testing.T) { setupChannelStatusTest(t) channel := Channel{ Name: "single-key-status", Key: "original-key", Status: common.ChannelStatusEnabled, Models: "original-model", Group: "default", UsedQuota: 100, ChannelInfo: ChannelInfo{}, } require.NoError(t, DB.Create(&channel).Error) stale, err := GetChannelById(channel.Id, true) require.NoError(t, err) concurrentChannelInfo := ChannelInfo{ IsMultiKey: true, MultiKeySize: 2, MultiKeyMode: constant.MultiKeyModePolling, MultiKeyPollingIndex: 1, } require.NoError(t, DB.Model(&Channel{}).Where("id = ?", channel.Id).Updates(map[string]any{ "key": "rotated-key", "used_quota": gorm.Expr("used_quota + ?", 250), "models": "concurrent-model", "channel_info": concurrentChannelInfo, }).Error) stale.Status = common.ChannelStatusManuallyDisabled stale.SetOtherInfo(map[string]any{ "status_reason": "manual operation", "status_time": int64(1234), }) require.NoError(t, stale.saveStatusState()) var stored Channel require.NoError(t, DB.First(&stored, channel.Id).Error) assert.Equal(t, common.ChannelStatusManuallyDisabled, stored.Status) assert.Equal(t, "rotated-key", stored.Key) assert.Equal(t, int64(350), stored.UsedQuota) assert.Equal(t, "concurrent-model", stored.Models) assert.Equal(t, concurrentChannelInfo, stored.ChannelInfo) otherInfo := stored.GetOtherInfo() assert.Equal(t, "manual operation", otherInfo["status_reason"]) assert.Equal(t, float64(1234), otherInfo["status_time"]) } func TestAutoDisableKeepsManualDisable(t *testing.T) { for _, cacheEnabled := range []bool{false, true} { for _, tc := range []struct { name string multiKey bool initialStatus int wantChanged bool wantStatus int }{ {name: "single key manual disable is kept", initialStatus: common.ChannelStatusManuallyDisabled, wantStatus: common.ChannelStatusManuallyDisabled}, {name: "multi key manual disable is kept", multiKey: true, initialStatus: common.ChannelStatusManuallyDisabled, wantStatus: common.ChannelStatusManuallyDisabled}, {name: "single key enabled is auto disabled", initialStatus: common.ChannelStatusEnabled, wantChanged: true, wantStatus: common.ChannelStatusAutoDisabled}, {name: "multi key enabled is auto disabled", multiKey: true, initialStatus: common.ChannelStatusEnabled, wantChanged: true, wantStatus: common.ChannelStatusAutoDisabled}, } { t.Run(fmt.Sprintf("cache=%t/%s", cacheEnabled, tc.name), func(t *testing.T) { setupChannelStatusTest(t) common.MemoryCacheEnabled = cacheEnabled channel := Channel{Name: t.Name(), Key: "key-a", Status: tc.initialStatus, Models: "test-model", Group: "default"} usingKey := "key-a" if tc.multiKey { // key-b already failed, so a failure on key-a disables every key. channel.Key = "key-a\nkey-b" channel.ChannelInfo = ChannelInfo{IsMultiKey: true, MultiKeySize: 2, MultiKeyStatusList: map[int]int{1: common.ChannelStatusAutoDisabled}} } channel.SetOtherInfo(map[string]any{"status_reason": "manual operation", "status_time": int64(1234)}) require.NoError(t, channel.Insert()) InitChannelCache() changed := UpdateChannelStatus(channel.Id, usingKey, common.ChannelStatusAutoDisabled, "provider rejected key") assert.Equal(t, tc.wantChanged, changed) stored, err := GetChannelById(channel.Id, true) require.NoError(t, err) assert.Equal(t, tc.wantStatus, stored.Status) if !tc.wantChanged { assert.Equal(t, "manual operation", stored.GetOtherInfo()["status_reason"]) assert.Equal(t, float64(1234), stored.GetOtherInfo()["status_time"]) assert.Equal(t, channel.ChannelInfo.MultiKeyStatusList, stored.ChannelInfo.MultiKeyStatusList) } var abilities []Ability require.NoError(t, DB.Where("channel_id = ?", channel.Id).Find(&abilities).Error) require.NotEmpty(t, abilities) for _, ability := range abilities { assert.False(t, ability.Enabled) } if cacheEnabled { cached, err := CacheGetChannel(channel.Id) require.NoError(t, err) assert.Equal(t, tc.wantStatus, cached.Status) assert.Equal(t, stored.ChannelInfo.MultiKeyStatusList, cached.ChannelInfo.MultiKeyStatusList) } }) } } }