test(group): 补充分组列表可用账号数与总账号数统计正确性的集成测试
修复 #2579 报告的可用账号数等于总数问题: 上游已通过 loadAccountCounts / GetAccountCount 两处 SQL 中的 COUNT(*) FILTER (WHERE status='active' AND schedulable=true) 正确区分可用账号,但缺少覆盖 active < total 场景的测试, 导致回归容易被忽略。 新增三个集成测试: - TestListWithFilters_ActiveAccountCount_LessThanTotal 含 active+schedulable、disabled、active+unschedulable 三类账号, 断言 AccountCount=3、ActiveAccountCount=1, 并验证 GetAccountCount 返回值与 ListWithFilters 字段一致。 - TestListWithFilters_RateLimitedAccountCount 验证 rate_limit_reset_at 未过期的账号计入 ActiveAccountCount(仍可调度), 同时单独出现在 RateLimitedAccountCount 中。 - TestListWithAccountCountSort_AttachesActiveCount 通过 SortBy=account_count 触发 listWithAccountCountSort 路径, 验证排序按 total 而非 active,且两个字段均被正确附加。 Fixes #2579
This commit is contained in:
parent
91da815993
commit
5465003d07
@ -651,6 +651,124 @@ func (s *GroupRepoSuite) TestGetAccountCount_Empty() {
|
||||
s.Require().Zero(count)
|
||||
}
|
||||
|
||||
// TestListWithFilters_ActiveAccountCount_LessThanTotal 验证 ActiveAccountCount 正确区分可用与不可用账号。
|
||||
// 当分组内存在 disabled 或 schedulable=false 的账号时,ActiveAccountCount 必须小于 AccountCount,
|
||||
// 且与 GetAccountCount 返回的 active 值一致。
|
||||
func (s *GroupRepoSuite) TestListWithFilters_ActiveAccountCount_LessThanTotal() {
|
||||
g := &service.Group{
|
||||
Name: "g-mixed-status",
|
||||
Platform: service.PlatformAnthropic,
|
||||
RateMultiplier: 1.0,
|
||||
IsExclusive: false,
|
||||
Status: service.StatusActive,
|
||||
SubscriptionType: service.SubscriptionTypeStandard,
|
||||
}
|
||||
s.Require().NoError(s.repo.Create(s.ctx, g))
|
||||
|
||||
insertAccount := func(name, status string, schedulable bool) int64 {
|
||||
var id int64
|
||||
s.Require().NoError(scanSingleRow(
|
||||
s.ctx, s.tx,
|
||||
"INSERT INTO accounts (name, platform, type, status, schedulable) VALUES ($1, $2, $3, $4, $5) RETURNING id",
|
||||
[]any{name, service.PlatformAnthropic, service.AccountTypeOAuth, status, schedulable},
|
||||
&id,
|
||||
))
|
||||
return id
|
||||
}
|
||||
link := func(accountID int64, priority int) {
|
||||
_, err := s.tx.ExecContext(s.ctx,
|
||||
"INSERT INTO account_groups (account_id, group_id, priority, created_at) VALUES ($1, $2, $3, NOW())",
|
||||
accountID, g.ID, priority)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
// account 1: active + schedulable → counts toward both total and active
|
||||
link(insertAccount("acc-active-sched", service.StatusActive, true), 1)
|
||||
// account 2: disabled → counts toward total only
|
||||
link(insertAccount("acc-disabled", service.StatusDisabled, true), 2)
|
||||
// account 3: active + not schedulable → counts toward total only
|
||||
link(insertAccount("acc-unschedulable", service.StatusActive, false), 3)
|
||||
|
||||
// --- ListWithFilters path ---
|
||||
isExclusive := false
|
||||
groups, _, err := s.repo.ListWithFilters(s.ctx,
|
||||
pagination.PaginationParams{Page: 1, PageSize: 100},
|
||||
service.PlatformAnthropic, service.StatusActive, "", &isExclusive)
|
||||
s.Require().NoError(err)
|
||||
|
||||
var found *service.Group
|
||||
for i := range groups {
|
||||
if groups[i].ID == g.ID {
|
||||
found = &groups[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
s.Require().NotNil(found, "created group must appear in ListWithFilters result")
|
||||
s.Assert().Equal(int64(3), found.AccountCount, "AccountCount must count all 3 accounts")
|
||||
s.Assert().Equal(int64(1), found.ActiveAccountCount, "ActiveAccountCount must count only the active+schedulable account")
|
||||
|
||||
// --- GetAccountCount must return identical values ---
|
||||
total, active, err := s.repo.GetAccountCount(s.ctx, g.ID)
|
||||
s.Require().NoError(err)
|
||||
s.Assert().Equal(found.AccountCount, total, "GetAccountCount total must match ListWithFilters AccountCount")
|
||||
s.Assert().Equal(found.ActiveAccountCount, active, "GetAccountCount active must match ListWithFilters ActiveAccountCount")
|
||||
}
|
||||
|
||||
// TestListWithFilters_RateLimitedAccountCount 验证 RateLimitedAccountCount 正确统计临时受限账号。
|
||||
// 受限账号(rate_limit_reset_at 尚未过期)仍然计入 ActiveAccountCount,
|
||||
// 同时额外出现在 RateLimitedAccountCount 中。
|
||||
func (s *GroupRepoSuite) TestListWithFilters_RateLimitedAccountCount() {
|
||||
g := &service.Group{
|
||||
Name: "g-rate-limited",
|
||||
Platform: service.PlatformAnthropic,
|
||||
RateMultiplier: 1.0,
|
||||
IsExclusive: false,
|
||||
Status: service.StatusActive,
|
||||
SubscriptionType: service.SubscriptionTypeStandard,
|
||||
}
|
||||
s.Require().NoError(s.repo.Create(s.ctx, g))
|
||||
|
||||
var normalID int64
|
||||
s.Require().NoError(scanSingleRow(s.ctx, s.tx,
|
||||
"INSERT INTO accounts (name, platform, type) VALUES ($1, $2, $3) RETURNING id",
|
||||
[]any{"acc-normal", service.PlatformAnthropic, service.AccountTypeOAuth},
|
||||
&normalID))
|
||||
|
||||
var rateLimitedID int64
|
||||
s.Require().NoError(scanSingleRow(s.ctx, s.tx,
|
||||
"INSERT INTO accounts (name, platform, type, rate_limit_reset_at) VALUES ($1, $2, $3, NOW() + INTERVAL '1 hour') RETURNING id",
|
||||
[]any{"acc-rate-limited", service.PlatformAnthropic, service.AccountTypeOAuth},
|
||||
&rateLimitedID))
|
||||
|
||||
_, err := s.tx.ExecContext(s.ctx,
|
||||
"INSERT INTO account_groups (account_id, group_id, priority, created_at) VALUES ($1, $2, $3, NOW())",
|
||||
normalID, g.ID, 1)
|
||||
s.Require().NoError(err)
|
||||
_, err = s.tx.ExecContext(s.ctx,
|
||||
"INSERT INTO account_groups (account_id, group_id, priority, created_at) VALUES ($1, $2, $3, NOW())",
|
||||
rateLimitedID, g.ID, 2)
|
||||
s.Require().NoError(err)
|
||||
|
||||
isExclusive := false
|
||||
groups, _, err := s.repo.ListWithFilters(s.ctx,
|
||||
pagination.PaginationParams{Page: 1, PageSize: 100},
|
||||
service.PlatformAnthropic, service.StatusActive, "", &isExclusive)
|
||||
s.Require().NoError(err)
|
||||
|
||||
var found *service.Group
|
||||
for i := range groups {
|
||||
if groups[i].ID == g.ID {
|
||||
found = &groups[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
s.Require().NotNil(found, "created group must appear in ListWithFilters result")
|
||||
s.Assert().Equal(int64(2), found.AccountCount, "AccountCount must be 2")
|
||||
// rate-limited account is still active+schedulable, so it counts toward active
|
||||
s.Assert().Equal(int64(2), found.ActiveAccountCount, "rate-limited account still counts as active")
|
||||
s.Assert().Equal(int64(1), found.RateLimitedAccountCount, "RateLimitedAccountCount must be 1")
|
||||
}
|
||||
|
||||
// --- DeleteAccountGroupsByGroupID ---
|
||||
|
||||
func (s *GroupRepoSuite) TestDeleteAccountGroupsByGroupID() {
|
||||
|
||||
@ -7,6 +7,67 @@ import (
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
)
|
||||
|
||||
// TestListWithAccountCountSort_AttachesActiveCount 验证通过 account_count 排序时,
|
||||
// ActiveAccountCount 与 AccountCount 都被正确附加到返回结果中,
|
||||
// 且排序基于 total 账号数而非 active 账号数。
|
||||
func (s *GroupRepoSuite) TestListWithAccountCountSort_AttachesActiveCount() {
|
||||
// Group A: 2 total, 1 active (1 disabled account)
|
||||
gA := &service.Group{Name: "sort-count-a", Platform: service.PlatformAnthropic, RateMultiplier: 1, Status: service.StatusActive, SubscriptionType: service.SubscriptionTypeStandard}
|
||||
// Group B: 1 total, 1 active
|
||||
gB := &service.Group{Name: "sort-count-b", Platform: service.PlatformAnthropic, RateMultiplier: 1, Status: service.StatusActive, SubscriptionType: service.SubscriptionTypeStandard}
|
||||
s.Require().NoError(s.repo.Create(s.ctx, gA))
|
||||
s.Require().NoError(s.repo.Create(s.ctx, gB))
|
||||
|
||||
insertAccount := func(name, status string) int64 {
|
||||
var id int64
|
||||
s.Require().NoError(scanSingleRow(s.ctx, s.tx,
|
||||
"INSERT INTO accounts (name, platform, type, status) VALUES ($1, $2, $3, $4) RETURNING id",
|
||||
[]any{name, service.PlatformAnthropic, service.AccountTypeOAuth, status},
|
||||
&id))
|
||||
return id
|
||||
}
|
||||
link := func(accountID, groupID int64, priority int) {
|
||||
_, err := s.tx.ExecContext(s.ctx,
|
||||
"INSERT INTO account_groups (account_id, group_id, priority, created_at) VALUES ($1, $2, $3, NOW())",
|
||||
accountID, groupID, priority)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
// gA: 1 active + 1 disabled → total=2, active=1
|
||||
link(insertAccount("sa-active", service.StatusActive), gA.ID, 1)
|
||||
link(insertAccount("sa-disabled", service.StatusDisabled), gA.ID, 2)
|
||||
// gB: 1 active → total=1, active=1
|
||||
link(insertAccount("sb-active", service.StatusActive), gB.ID, 1)
|
||||
|
||||
groups, _, err := s.repo.ListWithFilters(s.ctx, pagination.PaginationParams{
|
||||
Page: 1, PageSize: 100, SortBy: "account_count", SortOrder: "desc",
|
||||
}, service.PlatformAnthropic, service.StatusActive, "", nil)
|
||||
s.Require().NoError(err)
|
||||
|
||||
byID := make(map[int64]service.Group, len(groups))
|
||||
for _, g := range groups {
|
||||
byID[g.ID] = g
|
||||
}
|
||||
|
||||
s.Require().Contains(byID, gA.ID, "gA must appear in results")
|
||||
s.Require().Contains(byID, gB.ID, "gB must appear in results")
|
||||
|
||||
cA := byID[gA.ID]
|
||||
s.Assert().Equal(int64(2), cA.AccountCount, "gA AccountCount must be 2")
|
||||
s.Assert().Equal(int64(1), cA.ActiveAccountCount, "gA ActiveAccountCount must be 1")
|
||||
|
||||
cB := byID[gB.ID]
|
||||
s.Assert().Equal(int64(1), cB.AccountCount, "gB AccountCount must be 1")
|
||||
s.Assert().Equal(int64(1), cB.ActiveAccountCount, "gB ActiveAccountCount must be 1")
|
||||
|
||||
// Sort is by total (not active): gA (total=2) must rank higher than gB (total=1) in desc order
|
||||
indexByID := make(map[int64]int, len(groups))
|
||||
for i, g := range groups {
|
||||
indexByID[g.ID] = i
|
||||
}
|
||||
s.Assert().Less(indexByID[gA.ID], indexByID[gB.ID], "gA (total=2) must rank above gB (total=1) with account_count desc")
|
||||
}
|
||||
|
||||
func (s *GroupRepoSuite) TestList_DefaultSortBySortOrderAsc() {
|
||||
g1 := &service.Group{Name: "g1", Platform: service.PlatformAnthropic, RateMultiplier: 1, Status: service.StatusActive, SubscriptionType: service.SubscriptionTypeStandard, SortOrder: 20}
|
||||
g2 := &service.Group{Name: "g2", Platform: service.PlatformAnthropic, RateMultiplier: 1, Status: service.StatusActive, SubscriptionType: service.SubscriptionTypeStandard, SortOrder: 10}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user