Files
sub2api/backend/internal/service/gateway_account_selection_test.go

207 lines
6.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//go:build unit
package service
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
// --- helpers ---
func testTimePtr(t time.Time) *time.Time { return &t }
func makeAccWithLoad(id int64, priority int, loadRate int, lastUsed *time.Time, accType string) accountWithLoad {
return accountWithLoad{
account: &Account{
ID: id,
Priority: priority,
LastUsedAt: lastUsed,
Type: accType,
Schedulable: true,
Status: StatusActive,
},
loadInfo: &AccountLoadInfo{
AccountID: id,
CurrentConcurrency: 0,
LoadRate: loadRate,
},
}
}
// --- sortAccountsByPriorityAndLastUsed ---
func TestSortAccountsByPriorityAndLastUsed_ByPriority(t *testing.T) {
now := time.Now()
accounts := []*Account{
{ID: 1, Priority: 5, LastUsedAt: testTimePtr(now)},
{ID: 2, Priority: 1, LastUsedAt: testTimePtr(now)},
{ID: 3, Priority: 3, LastUsedAt: testTimePtr(now)},
}
sortAccountsByPriorityAndLastUsed(accounts, false)
require.Equal(t, int64(2), accounts[0].ID, "优先级最低的排第一")
require.Equal(t, int64(3), accounts[1].ID)
require.Equal(t, int64(1), accounts[2].ID)
}
func TestSortAccountsByPriorityAndLastUsed_SamePriorityByLastUsed(t *testing.T) {
now := time.Now()
accounts := []*Account{
{ID: 1, Priority: 1, LastUsedAt: testTimePtr(now)},
{ID: 2, Priority: 1, LastUsedAt: testTimePtr(now.Add(-1 * time.Hour))},
{ID: 3, Priority: 1, LastUsedAt: nil},
}
sortAccountsByPriorityAndLastUsed(accounts, false)
require.Equal(t, int64(3), accounts[0].ID, "nil LastUsedAt 排最前")
require.Equal(t, int64(2), accounts[1].ID, "更早使用的排前面")
require.Equal(t, int64(1), accounts[2].ID)
}
func TestSortAccountsByPriorityAndLastUsed_PreferOAuth(t *testing.T) {
accounts := []*Account{
{ID: 1, Priority: 1, LastUsedAt: nil, Type: AccountTypeAPIKey},
{ID: 2, Priority: 1, LastUsedAt: nil, Type: AccountTypeOAuth},
}
sortAccountsByPriorityAndLastUsed(accounts, true)
require.Equal(t, int64(2), accounts[0].ID, "preferOAuth 时 OAuth 账号排前面")
}
func TestSortAccountsByPriorityAndLastUsed_StableSort(t *testing.T) {
accounts := []*Account{
{ID: 1, Priority: 1, LastUsedAt: nil, Type: AccountTypeAPIKey},
{ID: 2, Priority: 1, LastUsedAt: nil, Type: AccountTypeAPIKey},
{ID: 3, Priority: 1, LastUsedAt: nil, Type: AccountTypeAPIKey},
}
// sortAccountsByPriorityAndLastUsed 内部会在同组(Priority+LastUsedAt)内做随机打散,
// 因此这里不再断言“稳定排序”。我们只验证:
// 1) 元素集合不变2) 多次运行能产生不同的顺序。
seenFirst := map[int64]bool{}
for i := 0; i < 100; i++ {
cpy := make([]*Account, len(accounts))
copy(cpy, accounts)
sortAccountsByPriorityAndLastUsed(cpy, false)
seenFirst[cpy[0].ID] = true
ids := map[int64]bool{}
for _, a := range cpy {
ids[a.ID] = true
}
require.True(t, ids[1] && ids[2] && ids[3])
}
require.GreaterOrEqual(t, len(seenFirst), 2, "同组账号应能被随机打散")
}
func TestSortAccountsByPriorityAndLastUsed_MixedPriorityAndTime(t *testing.T) {
now := time.Now()
accounts := []*Account{
{ID: 1, Priority: 2, LastUsedAt: nil},
{ID: 2, Priority: 1, LastUsedAt: testTimePtr(now)},
{ID: 3, Priority: 1, LastUsedAt: testTimePtr(now.Add(-1 * time.Hour))},
{ID: 4, Priority: 2, LastUsedAt: testTimePtr(now.Add(-2 * time.Hour))},
}
sortAccountsByPriorityAndLastUsed(accounts, false)
// 优先级1排前nil < earlier
require.Equal(t, int64(3), accounts[0].ID, "优先级1 + 更早")
require.Equal(t, int64(2), accounts[1].ID, "优先级1 + 现在")
// 优先级2排后nil < time
require.Equal(t, int64(1), accounts[2].ID, "优先级2 + nil")
require.Equal(t, int64(4), accounts[3].ID, "优先级2 + 有时间")
}
// --- filterByMinPriority ---
func TestFilterByMinPriority_Empty(t *testing.T) {
result := filterByMinPriority(nil)
require.Nil(t, result)
}
func TestFilterByMinPriority_SelectsMinPriority(t *testing.T) {
accounts := []accountWithLoad{
makeAccWithLoad(1, 5, 10, nil, AccountTypeAPIKey),
makeAccWithLoad(2, 1, 10, nil, AccountTypeAPIKey),
makeAccWithLoad(3, 1, 20, nil, AccountTypeAPIKey),
makeAccWithLoad(4, 2, 10, nil, AccountTypeAPIKey),
}
result := filterByMinPriority(accounts)
require.Len(t, result, 2)
require.Equal(t, int64(2), result[0].account.ID)
require.Equal(t, int64(3), result[1].account.ID)
}
// --- filterByMinLoadRate ---
func TestFilterByMinLoadRate_Empty(t *testing.T) {
result := filterByMinLoadRate(nil)
require.Nil(t, result)
}
func TestFilterByMinLoadRate_SelectsMinLoadRate(t *testing.T) {
accounts := []accountWithLoad{
makeAccWithLoad(1, 1, 30, nil, AccountTypeAPIKey),
makeAccWithLoad(2, 1, 10, nil, AccountTypeAPIKey),
makeAccWithLoad(3, 1, 10, nil, AccountTypeAPIKey),
makeAccWithLoad(4, 1, 20, nil, AccountTypeAPIKey),
}
result := filterByMinLoadRate(accounts)
require.Len(t, result, 2)
require.Equal(t, int64(2), result[0].account.ID)
require.Equal(t, int64(3), result[1].account.ID)
}
// --- selectByLRU ---
func TestSelectByLRU_Empty(t *testing.T) {
result := selectByLRU(nil, false)
require.Nil(t, result)
}
func TestSelectByLRU_Single(t *testing.T) {
accounts := []accountWithLoad{makeAccWithLoad(1, 1, 10, nil, AccountTypeAPIKey)}
result := selectByLRU(accounts, false)
require.NotNil(t, result)
require.Equal(t, int64(1), result.account.ID)
}
func TestSelectByLRU_NilLastUsedAtWins(t *testing.T) {
now := time.Now()
accounts := []accountWithLoad{
makeAccWithLoad(1, 1, 10, testTimePtr(now), AccountTypeAPIKey),
makeAccWithLoad(2, 1, 10, nil, AccountTypeAPIKey),
makeAccWithLoad(3, 1, 10, testTimePtr(now.Add(-1*time.Hour)), AccountTypeAPIKey),
}
result := selectByLRU(accounts, false)
require.NotNil(t, result)
require.Equal(t, int64(2), result.account.ID)
}
func TestSelectByLRU_EarliestTimeWins(t *testing.T) {
now := time.Now()
accounts := []accountWithLoad{
makeAccWithLoad(1, 1, 10, testTimePtr(now), AccountTypeAPIKey),
makeAccWithLoad(2, 1, 10, testTimePtr(now.Add(-1*time.Hour)), AccountTypeAPIKey),
makeAccWithLoad(3, 1, 10, testTimePtr(now.Add(-2*time.Hour)), AccountTypeAPIKey),
}
result := selectByLRU(accounts, false)
require.NotNil(t, result)
require.Equal(t, int64(3), result.account.ID)
}
func TestSelectByLRU_TiePreferOAuth(t *testing.T) {
now := time.Now()
// 账号 1/2 LastUsedAt 相同,且同为最小值。
accounts := []accountWithLoad{
makeAccWithLoad(1, 1, 10, testTimePtr(now), AccountTypeAPIKey),
makeAccWithLoad(2, 1, 10, testTimePtr(now), AccountTypeOAuth),
makeAccWithLoad(3, 1, 10, testTimePtr(now.Add(1*time.Hour)), AccountTypeAPIKey),
}
for i := 0; i < 50; i++ {
result := selectByLRU(accounts, true)
require.NotNil(t, result)
require.Equal(t, AccountTypeOAuth, result.account.Type)
require.Equal(t, int64(2), result.account.ID)
}
}