fix(grok): cool down accounts after 402

This commit is contained in:
Heatherm Huang
2026-07-23 00:06:04 +08:00
parent 63cef60594
commit ca0d3314cf
3 changed files with 58 additions and 1 deletions
@@ -594,6 +594,32 @@ func TestResponsesGrok429FailoverIsBounded(t *testing.T) {
})
}
func TestResponsesGrok402FailoverCooldown(t *testing.T) {
gin.SetMode(gin.TestMode)
_, repo, upstream, router, cleanup := newGrokCredentialFailoverHandler(t, "first_402")
defer cleanup()
recorder := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/openai/v1/responses", bytes.NewBufferString(`{"model":"grok","input":"hello","stream":false}`))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(recorder, req)
require.Equal(t, http.StatusOK, recorder.Code, recorder.Body.String())
require.Contains(t, recorder.Body.String(), "resp_healthy")
require.Equal(t, []int64{801, 802}, upstream.accountHits())
require.Equal(t, []int64{801}, repo.setTempIDs)
before := repo.selectorCalls()
second := httptest.NewRecorder()
secondReq := httptest.NewRequest(http.MethodPost, "/openai/v1/responses", bytes.NewBufferString(`{"model":"grok","input":"again","stream":false}`))
secondReq.Header.Set("Content-Type", "application/json")
router.ServeHTTP(second, secondReq)
require.Equal(t, http.StatusOK, second.Code, second.Body.String())
require.Equal(t, before+1, repo.selectorCalls())
require.Equal(t, []int64{801, 802, 802}, upstream.accountHits(), "cooldown must exclude the 402 account from later requests")
}
func TestResponsesGrok429FailoverHandlesMixedStatuses(t *testing.T) {
gin.SetMode(gin.TestMode)
@@ -836,7 +862,7 @@ func newGrokCredentialFailoverHandler(t *testing.T, mode string) (*OpenAIGateway
Extra: map[string]any{service.GrokMediaEligibleExtraKey: true},
},
}
if mode == "postmap_cancel" || mode == "first_429" || mode == "all_429" || mode == "mixed_429_500" || mode == "mixed_500_429" || mode == "oauth_429_apikey_500" {
if mode == "postmap_cancel" || mode == "first_402" || mode == "first_429" || mode == "all_429" || mode == "mixed_429_500" || mode == "mixed_500_429" || mode == "oauth_429_apikey_500" {
accounts[0].Credentials["expires_at"] = time.Now().Add(2 * time.Hour).UTC().Format(time.RFC3339)
}
if mode == "all_429" || mode == "mixed_429_500" || mode == "mixed_500_429" || mode == "oauth_429_apikey_500" {
@@ -879,6 +905,8 @@ func newGrokCredentialFailoverHandler(t *testing.T, mode string) (*OpenAIGateway
}
upstream := &grokCredentialHandlerUpstream{}
switch mode {
case "first_402":
upstream.failAccountID = 801
case "first_429":
upstream.rateLimitIDs = map[int64]bool{801: true}
case "all_429":
@@ -1353,6 +1353,8 @@ func (s *OpenAIGatewayService) handleGrokAccountUpstreamError(ctx context.Contex
switch statusCode {
case http.StatusUnauthorized:
s.tempUnscheduleGrok(ctx, account, 10*time.Minute, "grok credentials unauthorized")
case http.StatusPaymentRequired:
s.tempUnscheduleGrok(ctx, account, 30*time.Minute, "grok payment required")
case http.StatusForbidden:
if s.applyGrokForbiddenPolicy(ctx, account, responseBody) {
return
@@ -2439,6 +2439,13 @@ func TestHandleGrokAccountUpstreamErrorTempUnschedulesNonRateLimitStates(t *test
wantMinCooldown: 30*time.Minute - time.Second,
wantMaxCooldown: 30*time.Minute + time.Second,
},
{
name: "payment required",
status: http.StatusPaymentRequired,
wantReason: "grok payment required",
wantMinCooldown: 30*time.Minute - time.Second,
wantMaxCooldown: 30*time.Minute + time.Second,
},
{
name: "upstream temporary error",
status: http.StatusInternalServerError,
@@ -2483,6 +2490,26 @@ func TestHandleGrokAccountUpstreamError429SetsRateLimitedFromRetryAfter(t *testi
require.Zero(t, repo.tempUnschedCalls)
}
func TestHandleGrokAccountUpstreamError402RecoversAfterCooldownExpiry(t *testing.T) {
account := &Account{
ID: 610, Platform: PlatformGrok, Type: AccountTypeOAuth,
Status: StatusActive, Schedulable: true,
}
repo := &grokQuotaAccountRepo{}
svc := &OpenAIGatewayService{accountRepo: repo}
svc.handleGrokAccountUpstreamError(context.Background(), account, http.StatusPaymentRequired, nil, nil)
require.True(t, svc.isOpenAIAccountRuntimeBlocked(account))
require.Equal(t, 1, repo.tempUnschedCalls)
expired := time.Now().Add(-time.Second)
account.TempUnschedulableUntil = &expired
svc.openaiAccountRuntimeBlockUntil.Store(account.ID, expired)
require.False(t, svc.isOpenAIAccountRuntimeBlocked(account))
require.True(t, account.IsSchedulable())
}
func TestHandleGrokAccountUpstreamError429UsesLatestExhaustedWindowReset(t *testing.T) {
now := time.Now()
requestReset := now.Add(10 * time.Minute).Truncate(time.Second)