gateway_account_selection_test.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. //go:build unit
  2. package service
  3. import (
  4. "testing"
  5. "time"
  6. "github.com/stretchr/testify/require"
  7. )
  8. // --- helpers ---
  9. func testTimePtr(t time.Time) *time.Time { return &t }
  10. func makeAccWithLoad(id int64, priority int, loadRate int, lastUsed *time.Time, accType string) accountWithLoad {
  11. return accountWithLoad{
  12. account: &Account{
  13. ID: id,
  14. Priority: priority,
  15. LastUsedAt: lastUsed,
  16. Type: accType,
  17. Schedulable: true,
  18. Status: StatusActive,
  19. },
  20. loadInfo: &AccountLoadInfo{
  21. AccountID: id,
  22. CurrentConcurrency: 0,
  23. LoadRate: loadRate,
  24. },
  25. }
  26. }
  27. // --- sortAccountsByPriorityAndLastUsed ---
  28. func TestSortAccountsByPriorityAndLastUsed_ByPriority(t *testing.T) {
  29. now := time.Now()
  30. accounts := []*Account{
  31. {ID: 1, Priority: 5, LastUsedAt: testTimePtr(now)},
  32. {ID: 2, Priority: 1, LastUsedAt: testTimePtr(now)},
  33. {ID: 3, Priority: 3, LastUsedAt: testTimePtr(now)},
  34. }
  35. sortAccountsByPriorityAndLastUsed(accounts, false)
  36. require.Equal(t, int64(2), accounts[0].ID, "优先级最低的排第一")
  37. require.Equal(t, int64(3), accounts[1].ID)
  38. require.Equal(t, int64(1), accounts[2].ID)
  39. }
  40. func TestSortAccountsByPriorityAndLastUsed_SamePriorityByLastUsed(t *testing.T) {
  41. now := time.Now()
  42. accounts := []*Account{
  43. {ID: 1, Priority: 1, LastUsedAt: testTimePtr(now)},
  44. {ID: 2, Priority: 1, LastUsedAt: testTimePtr(now.Add(-1 * time.Hour))},
  45. {ID: 3, Priority: 1, LastUsedAt: nil},
  46. }
  47. sortAccountsByPriorityAndLastUsed(accounts, false)
  48. require.Equal(t, int64(3), accounts[0].ID, "nil LastUsedAt 排最前")
  49. require.Equal(t, int64(2), accounts[1].ID, "更早使用的排前面")
  50. require.Equal(t, int64(1), accounts[2].ID)
  51. }
  52. func TestSortAccountsByPriorityAndLastUsed_PreferOAuth(t *testing.T) {
  53. accounts := []*Account{
  54. {ID: 1, Priority: 1, LastUsedAt: nil, Type: AccountTypeAPIKey},
  55. {ID: 2, Priority: 1, LastUsedAt: nil, Type: AccountTypeOAuth},
  56. }
  57. sortAccountsByPriorityAndLastUsed(accounts, true)
  58. require.Equal(t, int64(2), accounts[0].ID, "preferOAuth 时 OAuth 账号排前面")
  59. }
  60. func TestSortAccountsByPriorityAndLastUsed_StableSort(t *testing.T) {
  61. accounts := []*Account{
  62. {ID: 1, Priority: 1, LastUsedAt: nil, Type: AccountTypeAPIKey},
  63. {ID: 2, Priority: 1, LastUsedAt: nil, Type: AccountTypeAPIKey},
  64. {ID: 3, Priority: 1, LastUsedAt: nil, Type: AccountTypeAPIKey},
  65. }
  66. // sortAccountsByPriorityAndLastUsed 内部会在同组(Priority+LastUsedAt)内做随机打散,
  67. // 因此这里不再断言“稳定排序”。我们只验证:
  68. // 1) 元素集合不变;2) 多次运行能产生不同的顺序。
  69. seenFirst := map[int64]bool{}
  70. for i := 0; i < 100; i++ {
  71. cpy := make([]*Account, len(accounts))
  72. copy(cpy, accounts)
  73. sortAccountsByPriorityAndLastUsed(cpy, false)
  74. seenFirst[cpy[0].ID] = true
  75. ids := map[int64]bool{}
  76. for _, a := range cpy {
  77. ids[a.ID] = true
  78. }
  79. require.True(t, ids[1] && ids[2] && ids[3])
  80. }
  81. require.GreaterOrEqual(t, len(seenFirst), 2, "同组账号应能被随机打散")
  82. }
  83. func TestSortAccountsByPriorityAndLastUsed_MixedPriorityAndTime(t *testing.T) {
  84. now := time.Now()
  85. accounts := []*Account{
  86. {ID: 1, Priority: 2, LastUsedAt: nil},
  87. {ID: 2, Priority: 1, LastUsedAt: testTimePtr(now)},
  88. {ID: 3, Priority: 1, LastUsedAt: testTimePtr(now.Add(-1 * time.Hour))},
  89. {ID: 4, Priority: 2, LastUsedAt: testTimePtr(now.Add(-2 * time.Hour))},
  90. }
  91. sortAccountsByPriorityAndLastUsed(accounts, false)
  92. // 优先级1排前:nil < earlier
  93. require.Equal(t, int64(3), accounts[0].ID, "优先级1 + 更早")
  94. require.Equal(t, int64(2), accounts[1].ID, "优先级1 + 现在")
  95. // 优先级2排后:nil < time
  96. require.Equal(t, int64(1), accounts[2].ID, "优先级2 + nil")
  97. require.Equal(t, int64(4), accounts[3].ID, "优先级2 + 有时间")
  98. }
  99. // --- filterByMinPriority ---
  100. func TestFilterByMinPriority_Empty(t *testing.T) {
  101. result := filterByMinPriority(nil)
  102. require.Nil(t, result)
  103. }
  104. func TestFilterByMinPriority_SelectsMinPriority(t *testing.T) {
  105. accounts := []accountWithLoad{
  106. makeAccWithLoad(1, 5, 10, nil, AccountTypeAPIKey),
  107. makeAccWithLoad(2, 1, 10, nil, AccountTypeAPIKey),
  108. makeAccWithLoad(3, 1, 20, nil, AccountTypeAPIKey),
  109. makeAccWithLoad(4, 2, 10, nil, AccountTypeAPIKey),
  110. }
  111. result := filterByMinPriority(accounts)
  112. require.Len(t, result, 2)
  113. require.Equal(t, int64(2), result[0].account.ID)
  114. require.Equal(t, int64(3), result[1].account.ID)
  115. }
  116. // --- filterByMinLoadRate ---
  117. func TestFilterByMinLoadRate_Empty(t *testing.T) {
  118. result := filterByMinLoadRate(nil)
  119. require.Nil(t, result)
  120. }
  121. func TestFilterByMinLoadRate_SelectsMinLoadRate(t *testing.T) {
  122. accounts := []accountWithLoad{
  123. makeAccWithLoad(1, 1, 30, nil, AccountTypeAPIKey),
  124. makeAccWithLoad(2, 1, 10, nil, AccountTypeAPIKey),
  125. makeAccWithLoad(3, 1, 10, nil, AccountTypeAPIKey),
  126. makeAccWithLoad(4, 1, 20, nil, AccountTypeAPIKey),
  127. }
  128. result := filterByMinLoadRate(accounts)
  129. require.Len(t, result, 2)
  130. require.Equal(t, int64(2), result[0].account.ID)
  131. require.Equal(t, int64(3), result[1].account.ID)
  132. }
  133. // --- selectByLRU ---
  134. func TestSelectByLRU_Empty(t *testing.T) {
  135. result := selectByLRU(nil, false)
  136. require.Nil(t, result)
  137. }
  138. func TestSelectByLRU_Single(t *testing.T) {
  139. accounts := []accountWithLoad{makeAccWithLoad(1, 1, 10, nil, AccountTypeAPIKey)}
  140. result := selectByLRU(accounts, false)
  141. require.NotNil(t, result)
  142. require.Equal(t, int64(1), result.account.ID)
  143. }
  144. func TestSelectByLRU_NilLastUsedAtWins(t *testing.T) {
  145. now := time.Now()
  146. accounts := []accountWithLoad{
  147. makeAccWithLoad(1, 1, 10, testTimePtr(now), AccountTypeAPIKey),
  148. makeAccWithLoad(2, 1, 10, nil, AccountTypeAPIKey),
  149. makeAccWithLoad(3, 1, 10, testTimePtr(now.Add(-1*time.Hour)), AccountTypeAPIKey),
  150. }
  151. result := selectByLRU(accounts, false)
  152. require.NotNil(t, result)
  153. require.Equal(t, int64(2), result.account.ID)
  154. }
  155. func TestSelectByLRU_EarliestTimeWins(t *testing.T) {
  156. now := time.Now()
  157. accounts := []accountWithLoad{
  158. makeAccWithLoad(1, 1, 10, testTimePtr(now), AccountTypeAPIKey),
  159. makeAccWithLoad(2, 1, 10, testTimePtr(now.Add(-1*time.Hour)), AccountTypeAPIKey),
  160. makeAccWithLoad(3, 1, 10, testTimePtr(now.Add(-2*time.Hour)), AccountTypeAPIKey),
  161. }
  162. result := selectByLRU(accounts, false)
  163. require.NotNil(t, result)
  164. require.Equal(t, int64(3), result.account.ID)
  165. }
  166. func TestSelectByLRU_TiePreferOAuth(t *testing.T) {
  167. now := time.Now()
  168. // 账号 1/2 LastUsedAt 相同,且同为最小值。
  169. accounts := []accountWithLoad{
  170. makeAccWithLoad(1, 1, 10, testTimePtr(now), AccountTypeAPIKey),
  171. makeAccWithLoad(2, 1, 10, testTimePtr(now), AccountTypeOAuth),
  172. makeAccWithLoad(3, 1, 10, testTimePtr(now.Add(1*time.Hour)), AccountTypeAPIKey),
  173. }
  174. for i := 0; i < 50; i++ {
  175. result := selectByLRU(accounts, true)
  176. require.NotNil(t, result)
  177. require.Equal(t, AccountTypeOAuth, result.account.Type)
  178. require.Equal(t, int64(2), result.account.ID)
  179. }
  180. }