This commit is contained in:
2026-08-16 18:59:29 +02:00
parent b617342601
commit d6fe7c38d5
5 changed files with 215 additions and 27 deletions
@@ -3,6 +3,7 @@ package processing
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
@@ -226,3 +227,86 @@ func TestOpenAIClient_doComplete_emptyContentLengthRetriesWithBudget(t *testing.
t.Fatalf("text=%q", comp.Text)
}
}
func TestOpenAIClient_Complete_timeoutNotRetried(t *testing.T) {
calls := 0
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls++
deadline := time.After(300 * time.Millisecond)
for {
select {
case <-r.Context().Done():
return
case <-deadline:
_ = json.NewEncoder(w).Encode(map[string]any{
"choices": []map[string]any{
{"message": map[string]any{"content": "late"}},
},
})
return
case <-time.After(10 * time.Millisecond):
}
}
}))
defer srv.Close()
c := NewOpenAIClient("test-key", srv.URL, "code-fast", 0, 3)
c.MaxRetries = 3
c.HTTPClient = &http.Client{Timeout: 60 * time.Millisecond}
start := time.Now()
_, err := c.Complete(context.Background(), "sys", "user")
elapsed := time.Since(start)
if err == nil {
t.Fatal("expected timeout error")
}
if !strings.Contains(err.Error(), "timed out waiting for model") {
t.Fatalf("want clear timeout message, got: %v", err)
}
if calls != 1 {
t.Fatalf("calls=%d want 1 (timeout must not retry)", calls)
}
if elapsed > time.Second {
t.Fatalf("timeout too slow: %s", elapsed)
}
}
func TestOpenAIClient_Complete_emptyAtReasoningBudgetNotRetried(t *testing.T) {
t.Parallel()
calls := 0
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls++
_ = json.NewEncoder(w).Encode(map[string]any{
"model": "code-fast",
"choices": []map[string]any{{
"finish_reason": "length",
"message": map[string]any{"role": "assistant", "content": "", "reasoning_content": "still thinking"},
}},
"usage": map[string]int{"prompt_tokens": 1, "completion_tokens": 4096, "total_tokens": 4097},
})
}))
defer srv.Close()
c := NewOpenAIClient("test-key", srv.URL, "code-fast", 0, 3)
c.HTTPClient = srv.Client()
_, err := c.CompleteWithOptions(context.Background(), "sys", "user", CompleteOptions{MaxTokens: maxTokensReasoningBudget})
if err == nil {
t.Fatal("expected empty response error")
}
if !strings.Contains(err.Error(), "empty response") {
t.Fatalf("err=%v", err)
}
if calls != 1 {
t.Fatalf("calls=%d want 1 (no retry at reasoning budget)", calls)
}
}
func TestIsOpenAITimeoutErr(t *testing.T) {
t.Parallel()
if !isOpenAITimeoutErr(context.DeadlineExceeded) {
t.Fatal("DeadlineExceeded")
}
if !isOpenAITimeoutErr(fmt.Errorf("Get \"http://x\": context deadline exceeded (Client.Timeout exceeded while awaiting headers)")) {
t.Fatal("Client.Timeout message")
}
if isOpenAITimeoutErr(fmt.Errorf("connection refused")) {
t.Fatal("connection refused should not be timeout")
}
}