Merge pull request #5053 from wucm667/fix/issue-5015-skip-unschedulable-refresh

fix(account): skip unschedulable token refresh candidates
This commit is contained in:
Wesley Liddick
2026-07-31 11:43:18 +08:00
committed by GitHub
4 changed files with 26 additions and 0 deletions
@@ -1107,6 +1107,7 @@ func (r *accountRepository) ListOAuthRefreshCandidatePage(ctx context.Context, o
SELECT id
FROM accounts
WHERE deleted_at IS NULL
AND schedulable = TRUE
AND platform = ANY($1)
AND id > $2`
if options.ActiveOnly {
@@ -327,6 +327,14 @@ func (s *AccountRepoSuite) TestListOAuthRefreshCandidatePage_GrokCursorAndExclus
"expires_at": now.Add(30 * time.Minute).Format(time.RFC3339),
},
})
unschedulable := mustCreateAccount(s.T(), s.client, &service.Account{
Name: "grok-oauth-unschedulable-excluded",
Platform: service.PlatformGrok,
Type: service.AccountTypeOAuth,
Status: service.StatusActive,
Credentials: map[string]any{"refresh_token": "refresh-unschedulable"},
})
s.Require().NoError(s.client.Account.UpdateOneID(unschedulable.ID).SetSchedulable(false).Exec(s.ctx))
mustCreateAccount(s.T(), s.client, &service.Account{
Name: "grok-api-key-excluded",
Platform: service.PlatformGrok,
@@ -386,6 +394,7 @@ func (s *AccountRepoSuite) TestListOAuthRefreshCandidatePage_GrokCursorAndExclus
first := firstPage.Accounts
s.Require().Len(first, 2)
s.Require().Equal([]int64{valid1.ID, valid2.ID}, []int64{first[0].ID, first[1].ID})
s.Require().NotContains([]int64{first[0].ID, first[1].ID}, unschedulable.ID)
options.AfterID = first[len(first)-1].ID
secondPage, err := s.repo.ListOAuthRefreshCandidatePage(s.ctx, options)
@@ -276,6 +276,8 @@ func TestAccountRepository_ListOAuthRefreshCandidatePage_SQLFilter(t *testing.T)
normalized := normalizeSQLWhitespace(capturedSQL)
require.Contains(t, normalized, "deleted_at IS NULL")
require.Contains(t, normalized, "schedulable = TRUE",
"permanently unschedulable accounts must not remain OAuth refresh candidates")
require.Contains(t, normalized, "status = 'active'")
// setup-token 的 access_token 同为 8h 短期令牌,必须与 oauth 一起纳入后台刷新候选
require.Contains(t, normalized, "type IN ('oauth', 'setup-token')")
@@ -51,6 +51,7 @@ func (r *tokenRefreshCandidateRepo) ListOAuthRefreshCandidatePage(_ context.Cont
}
}
if options.ActiveOnly && account.Status != StatusActive ||
!account.Schedulable ||
account.Type != AccountTypeOAuth ||
!platformAllowed ||
options.RequireRefreshToken && strings.TrimSpace(refreshToken) == "" ||
@@ -122,6 +123,7 @@ func TestTokenRefreshService_ProcessRefreshUsesOAuthRefreshCandidates(t *testing
Platform: PlatformOpenAI,
Type: AccountTypeOAuth,
Status: StatusActive,
Schedulable: true,
Credentials: map[string]any{"refresh_token": "refresh-token"},
},
{
@@ -129,6 +131,7 @@ func TestTokenRefreshService_ProcessRefreshUsesOAuthRefreshCandidates(t *testing
Platform: PlatformOpenAI,
Type: AccountTypeOAuth,
Status: StatusActive,
Schedulable: true,
Credentials: map[string]any{},
},
{
@@ -143,6 +146,7 @@ func TestTokenRefreshService_ProcessRefreshUsesOAuthRefreshCandidates(t *testing
Platform: PlatformAntigravity,
Type: AccountTypeOAuth,
Status: StatusActive,
Schedulable: true,
Credentials: map[string]any{"refresh_token": "refresh-token"},
TempUnschedulableUntil: &future,
TempUnschedulableReason: "token refresh retry exhausted: network timeout",
@@ -152,6 +156,7 @@ func TestTokenRefreshService_ProcessRefreshUsesOAuthRefreshCandidates(t *testing
Platform: "other",
Type: AccountTypeOAuth,
Status: StatusActive,
Schedulable: true,
Credentials: map[string]any{"refresh_token": "refresh-token"},
},
{
@@ -159,11 +164,20 @@ func TestTokenRefreshService_ProcessRefreshUsesOAuthRefreshCandidates(t *testing
Platform: PlatformAntigravity,
Type: AccountTypeOAuth,
Status: StatusActive,
Schedulable: true,
Credentials: map[string]any{"refresh_token": "refresh-token"},
Extra: map[string]any{"privacy_mode": AntigravityPrivacySet},
TempUnschedulableUntil: &future,
TempUnschedulableReason: "OAuth 401: unauthorized",
},
{
ID: 7,
Platform: PlatformOpenAI,
Type: AccountTypeOAuth,
Status: StatusActive,
Schedulable: false,
Credentials: map[string]any{"refresh_token": "permanently-rejected-token"},
},
},
}
svc := &TokenRefreshService{