2026-02-08 12:05:39 +08:00
|
|
|
|
//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},
|
|
|
|
|
|
}
|
2026-02-09 21:35:41 +08:00
|
|
|
|
|
|
|
|
|
|
// 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, "同组账号应能被随机打散")
|
2026-02-08 12:05:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 + 有时间")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 21:35:41 +08:00
|
|
|
|
// --- filterByMinPriority ---
|
2026-02-08 12:05:39 +08:00
|
|
|
|
|
2026-02-09 21:35:41 +08:00
|
|
|
|
func TestFilterByMinPriority_Empty(t *testing.T) {
|
|
|
|
|
|
result := filterByMinPriority(nil)
|
2026-02-08 12:05:39 +08:00
|
|
|
|
require.Nil(t, result)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 21:35:41 +08:00
|
|
|
|
func TestFilterByMinPriority_SelectsMinPriority(t *testing.T) {
|
2026-02-08 12:05:39 +08:00
|
|
|
|
accounts := []accountWithLoad{
|
2026-02-09 21:35:41 +08:00
|
|
|
|
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)
|
2026-02-08 12:05:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 21:35:41 +08:00
|
|
|
|
// --- filterByMinLoadRate ---
|
|
|
|
|
|
|
|
|
|
|
|
func TestFilterByMinLoadRate_Empty(t *testing.T) {
|
|
|
|
|
|
result := filterByMinLoadRate(nil)
|
|
|
|
|
|
require.Nil(t, result)
|
2026-02-08 12:05:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 21:35:41 +08:00
|
|
|
|
func TestFilterByMinLoadRate_SelectsMinLoadRate(t *testing.T) {
|
2026-02-08 12:05:39 +08:00
|
|
|
|
accounts := []accountWithLoad{
|
2026-02-09 21:35:41 +08:00
|
|
|
|
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)
|
2026-02-08 12:05:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 21:35:41 +08:00
|
|
|
|
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()
|
2026-02-08 12:05:39 +08:00
|
|
|
|
accounts := []accountWithLoad{
|
2026-02-09 21:35:41 +08:00
|
|
|
|
makeAccWithLoad(1, 1, 10, testTimePtr(now), AccountTypeAPIKey),
|
|
|
|
|
|
makeAccWithLoad(2, 1, 10, nil, AccountTypeAPIKey),
|
|
|
|
|
|
makeAccWithLoad(3, 1, 10, testTimePtr(now.Add(-1*time.Hour)), AccountTypeAPIKey),
|
2026-02-08 12:05:39 +08:00
|
|
|
|
}
|
2026-02-09 21:35:41 +08:00
|
|
|
|
result := selectByLRU(accounts, false)
|
|
|
|
|
|
require.NotNil(t, result)
|
|
|
|
|
|
require.Equal(t, int64(2), result.account.ID)
|
2026-02-08 12:05:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 21:35:41 +08:00
|
|
|
|
func TestSelectByLRU_EarliestTimeWins(t *testing.T) {
|
|
|
|
|
|
now := time.Now()
|
2026-02-08 12:05:39 +08:00
|
|
|
|
accounts := []accountWithLoad{
|
2026-02-09 21:35:41 +08:00
|
|
|
|
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),
|
2026-02-08 12:05:39 +08:00
|
|
|
|
}
|
2026-02-09 21:35:41 +08:00
|
|
|
|
result := selectByLRU(accounts, false)
|
|
|
|
|
|
require.NotNil(t, result)
|
|
|
|
|
|
require.Equal(t, int64(3), result.account.ID)
|
2026-02-08 12:05:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 21:35:41 +08:00
|
|
|
|
func TestSelectByLRU_TiePreferOAuth(t *testing.T) {
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
|
// 账号 1/2 LastUsedAt 相同,且同为最小值。
|
2026-02-08 12:05:39 +08:00
|
|
|
|
accounts := []accountWithLoad{
|
2026-02-09 21:35:41 +08:00
|
|
|
|
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),
|
2026-02-08 12:05:39 +08:00
|
|
|
|
}
|
2026-02-09 21:35:41 +08:00
|
|
|
|
for i := 0; i < 50; i++ {
|
|
|
|
|
|
result := selectByLRU(accounts, true)
|
2026-02-08 12:05:39 +08:00
|
|
|
|
require.NotNil(t, result)
|
2026-02-09 21:35:41 +08:00
|
|
|
|
require.Equal(t, AccountTypeOAuth, result.account.Type)
|
|
|
|
|
|
require.Equal(t, int64(2), result.account.ID)
|
2026-02-08 12:05:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|