mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-21 14:19:18 +08:00
fix(account-repo): refresh candidates SQL excluded healthy accounts; fix CI build
Post-merge audit of #3272 found two regressions: 1. ListOAuthRefreshCandidates used "AND NOT (a AND b)" which, under PG 3-valued logic, evaluates to NULL when both temp_unschedulable_until and temp_unschedulable_reason are NULL — i.e., the common healthy account state. Such rows were silently excluded from the background token refresh worker, so their OAuth access tokens would never get refreshed and eventually start returning 401. Verified empirically against PostgreSQL: only 3 of 5 test rows matched before the fix; after switching to "(a AND b) IS NOT TRUE" the expected 4 rows match. 2. The new ListOAuthRefreshCandidates method on AccountRepository was not implemented on stubAccountRepo in api_contract_test.go (build tag "unit"), breaking "make test-unit" which CI runs in .github/workflows/backend-ci.yml. Tests: - Added IS NOT TRUE and "AND NOT (" assertions to the SQL-shape unit test so the predicate can't regress to the broken form again. - "go test -tags=unit ./internal/..." now passes cleanly.
This commit is contained in:
@@ -653,6 +653,10 @@ func (r *accountRepository) ListOAuthRefreshCandidates(ctx context.Context) ([]s
|
||||
if r.sql == nil {
|
||||
return nil, errors.New("account repository SQL executor not configured")
|
||||
}
|
||||
// (cond) IS NOT TRUE 把 NULL 和 FALSE 都视为"可被刷新"。直接写
|
||||
// NOT (a AND b) 在 PG 三值逻辑下会把 a 或 b 为 NULL 的行(即绝大多数
|
||||
// 健康账号:temp_unschedulable_until=NULL)也排除,导致后台 token
|
||||
// 刷新工作器漏掉所有正常账号 → access_token 到期后请求开始 401。
|
||||
rows, err := r.sql.QueryContext(ctx, `
|
||||
SELECT id
|
||||
FROM accounts
|
||||
@@ -662,10 +666,10 @@ func (r *accountRepository) ListOAuthRefreshCandidates(ctx context.Context) ([]s
|
||||
AND platform IN ('anthropic', 'openai', 'gemini', 'antigravity')
|
||||
AND credentials ? 'refresh_token'
|
||||
AND btrim(credentials->>'refresh_token') <> ''
|
||||
AND NOT (
|
||||
AND (
|
||||
temp_unschedulable_until > NOW()
|
||||
AND temp_unschedulable_reason LIKE 'token refresh retry exhausted:%'
|
||||
)
|
||||
) IS NOT TRUE
|
||||
ORDER BY priority ASC, id ASC
|
||||
`)
|
||||
if err != nil {
|
||||
|
||||
@@ -49,6 +49,10 @@ func TestAccountRepository_ListOAuthRefreshCandidates_SQLFilter(t *testing.T) {
|
||||
require.Contains(t, normalized, "btrim(credentials->>'refresh_token') <> ''")
|
||||
require.Contains(t, normalized, "temp_unschedulable_until > NOW()")
|
||||
require.Contains(t, normalized, "temp_unschedulable_reason LIKE 'token refresh retry exhausted:%'")
|
||||
require.Contains(t, normalized, "IS NOT TRUE",
|
||||
"must use IS NOT TRUE so accounts with NULL temp_unschedulable_until are not silently excluded by PG 3-valued logic")
|
||||
require.NotContains(t, normalized, "AND NOT (",
|
||||
"plain NOT (...) excludes NULL temp_unschedulable_until rows (the common healthy case)")
|
||||
require.Contains(t, normalized, "ORDER BY priority ASC, id ASC")
|
||||
require.NotContains(t, normalized, "credentials->>'expires_at'")
|
||||
require.NoError(t, mock.ExpectationsWereMet())
|
||||
|
||||
@@ -1681,6 +1681,10 @@ func (s *stubAccountRepo) ListActive(ctx context.Context) ([]service.Account, er
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (s *stubAccountRepo) ListOAuthRefreshCandidates(ctx context.Context) ([]service.Account, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (s *stubAccountRepo) ListByPlatform(ctx context.Context, platform string) ([]service.Account, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user