Skip to content

Commit 4a94178

Browse files
committed
modelerrors: add missing test coverage
Add tests for: - ClassifyModelError with ContextOverflowError wrapping a StatusError - ExtractHTTPStatusCode with *StatusError in the error chain - FormatError with a wrapped ContextOverflowError (via fmt.Errorf) - NewContextOverflowError constructor usage - ContextOverflowError with nil Underlying (Unwrap returns nil) Assisted-By: docker-agent
1 parent 6512b13 commit 4a94178

1 file changed

Lines changed: 36 additions & 5 deletions

File tree

pkg/modelerrors/modelerrors_test.go

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ func TestExtractHTTPStatusCode(t *testing.T) {
8585
{name: "502 in message", err: errors.New("502 bad gateway"), expected: 502},
8686
{name: "401 in message", err: errors.New("401 unauthorized"), expected: 401},
8787
{name: "no status code", err: errors.New("connection refused"), expected: 0},
88+
// StatusError structural path
89+
{name: "StatusError 429", err: &StatusError{StatusCode: 429, Err: errors.New("rate limited")}, expected: 429},
90+
{name: "StatusError 500", err: &StatusError{StatusCode: 500, Err: errors.New("server error")}, expected: 500},
91+
{name: "wrapped StatusError", err: fmt.Errorf("outer: %w", &StatusError{StatusCode: 503, Err: errors.New("unavailable")}), expected: 503},
8892
}
8993

9094
for _, tt := range tests {
@@ -159,20 +163,28 @@ func TestContextOverflowError(t *testing.T) {
159163
t.Run("wraps underlying error", func(t *testing.T) {
160164
t.Parallel()
161165
underlying := errors.New("prompt is too long: 226360 tokens > 200000 maximum")
162-
ctxErr := &ContextOverflowError{Underlying: underlying}
166+
ctxErr := NewContextOverflowError(underlying)
163167

164168
assert.Contains(t, ctxErr.Error(), "context window overflow")
165169
assert.Contains(t, ctxErr.Error(), "prompt is too long")
166170
assert.ErrorIs(t, ctxErr, underlying)
167171
})
168172

169-
t.Run("errors.As works", func(t *testing.T) {
173+
t.Run("nil underlying returns fallback message", func(t *testing.T) {
174+
t.Parallel()
175+
ctxErr := NewContextOverflowError(nil)
176+
assert.Equal(t, "context window overflow", ctxErr.Error())
177+
assert.NoError(t, ctxErr.Unwrap())
178+
})
179+
180+
t.Run("errors.As works through wrapping", func(t *testing.T) {
170181
t.Parallel()
171182
underlying := errors.New("test error")
172-
wrapped := fmt.Errorf("all models failed: %w", &ContextOverflowError{Underlying: underlying})
183+
wrapped := fmt.Errorf("all models failed: %w", NewContextOverflowError(underlying))
173184

174185
var ctxErr *ContextOverflowError
175-
assert.ErrorAs(t, wrapped, &ctxErr)
186+
require.ErrorAs(t, wrapped, &ctxErr)
187+
assert.Equal(t, underlying, ctxErr.Underlying)
176188
})
177189
}
178190

@@ -207,13 +219,21 @@ func TestFormatError(t *testing.T) {
207219

208220
t.Run("context overflow shows user-friendly message", func(t *testing.T) {
209221
t.Parallel()
210-
err := &ContextOverflowError{Underlying: errors.New("prompt is too long")}
222+
err := NewContextOverflowError(errors.New("prompt is too long"))
211223
msg := FormatError(err)
212224
assert.Contains(t, msg, "context window")
213225
assert.Contains(t, msg, "/compact")
214226
assert.NotContains(t, msg, "prompt is too long")
215227
})
216228

229+
t.Run("wrapped context overflow shows user-friendly message", func(t *testing.T) {
230+
t.Parallel()
231+
err := fmt.Errorf("outer: %w", NewContextOverflowError(errors.New("prompt is too long")))
232+
msg := FormatError(err)
233+
assert.Contains(t, msg, "context window")
234+
assert.Contains(t, msg, "/compact")
235+
})
236+
217237
t.Run("generic error preserves message", func(t *testing.T) {
218238
t.Parallel()
219239
err := errors.New("authentication failed")
@@ -404,4 +424,15 @@ func TestClassifyModelError(t *testing.T) {
404424
assert.True(t, rateLimited)
405425
assert.Equal(t, 15*time.Second, retryAfterOut)
406426
})
427+
428+
t.Run("ContextOverflowError wrapping a StatusError is not retryable", func(t *testing.T) {
429+
t.Parallel()
430+
// A 400 StatusError whose message also triggers context overflow detection
431+
statusErr := &StatusError{StatusCode: 400, Err: errors.New("prompt is too long")}
432+
ctxErr := NewContextOverflowError(statusErr)
433+
retryable, rateLimited, retryAfter := ClassifyModelError(ctxErr)
434+
assert.False(t, retryable, "context overflow should never be retryable")
435+
assert.False(t, rateLimited)
436+
assert.Equal(t, time.Duration(0), retryAfter)
437+
})
407438
}

0 commit comments

Comments
 (0)