@@ -2,6 +2,8 @@ package auth
22
33import (
44 "context"
5+ "fmt"
6+ "io"
57 "net/http"
68 "sync"
79 "testing"
@@ -108,6 +110,55 @@ func TestManager_ShouldRetryAfterError_UsesOAuthModelAliasForCooldown(t *testing
108110 }
109111}
110112
113+ func TestManager_ShouldRetryAfterError_RetriesUnexpectedEOF (t * testing.T ) {
114+ m := NewManager (nil , nil , nil )
115+ m .SetRetryConfig (3 , 30 * time .Second , 0 )
116+
117+ model := "test-model"
118+ auth := & Auth {ID : "auth-1" , Provider : "claude" }
119+ if _ , errRegister := m .Register (context .Background (), auth ); errRegister != nil {
120+ t .Fatalf ("register auth: %v" , errRegister )
121+ }
122+
123+ _ , _ , maxWait := m .retrySettings ()
124+
125+ // A statusless "unexpected EOF" error (as produced by a truncated upstream
126+ // stream) must be retried immediately rather than surfaced to the client.
127+ wait , shouldRetry := m .shouldRetryAfterError (& Error {Message : "unexpected EOF" }, 0 , []string {"claude" }, model , maxWait )
128+ if ! shouldRetry {
129+ t .Fatalf ("expected shouldRetry=true for unexpected EOF, got false" )
130+ }
131+ if wait != 0 {
132+ t .Fatalf ("expected immediate retry (wait=0), got %v" , wait )
133+ }
134+
135+ // io.ErrUnexpectedEOF (possibly wrapped) is detected as well.
136+ wrapped := fmt .Errorf ("read stream: %w" , io .ErrUnexpectedEOF )
137+ if _ , shouldRetry = m .shouldRetryAfterError (wrapped , 0 , []string {"claude" }, model , maxWait ); ! shouldRetry {
138+ t .Fatalf ("expected shouldRetry=true for wrapped io.ErrUnexpectedEOF, got false" )
139+ }
140+
141+ // Retries stop once the configured request-retry count is exhausted.
142+ if _ , shouldRetry = m .shouldRetryAfterError (& Error {Message : "unexpected EOF" }, 3 , []string {"claude" }, model , maxWait ); shouldRetry {
143+ t .Fatalf ("expected shouldRetry=false on attempt=3 for request_retry=3, got true" )
144+ }
145+ }
146+
147+ func TestManager_ShouldRetryAfterError_UnexpectedEOFRespectsRetryDisabled (t * testing.T ) {
148+ m := NewManager (nil , nil , nil )
149+ m .SetRetryConfig (0 , 30 * time .Second , 0 )
150+
151+ auth := & Auth {ID : "auth-1" , Provider : "claude" }
152+ if _ , errRegister := m .Register (context .Background (), auth ); errRegister != nil {
153+ t .Fatalf ("register auth: %v" , errRegister )
154+ }
155+
156+ _ , _ , maxWait := m .retrySettings ()
157+ if _ , shouldRetry := m .shouldRetryAfterError (& Error {Message : "unexpected EOF" }, 0 , []string {"claude" }, "test-model" , maxWait ); shouldRetry {
158+ t .Fatalf ("expected shouldRetry=false when request-retry=0, got true" )
159+ }
160+ }
161+
111162type credentialRetryLimitExecutor struct {
112163 id string
113164
0 commit comments