| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- //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)
- }
- }
|