返回 DeepSeek-Reasonix
quota_error_test.go
根目录 / internal / provider / quota_error_test.go
1 package provider
2
3 import (
4 "context"
5 "errors"
6 "net/http"
7 "net/http/httptest"
8 "strings"
9 "testing"
10 )
11
12 func TestObservedQuotaResponsesNeverRetryOrBlameCredentials(t *testing.T) {
13 for _, tc := range []struct {
14 status int
15 body, code string
16 }{
17 {401, `{"error":{"type":"CreditsError","message":"Insufficient balance. https://example.test/private-billing"}}`, "CreditsError"},
18 {402, `{"error":{"code":"too_many_requests","message":"Call failed: Insufficient token quota.","type":"rate_limit_error"}}`, "too_many_requests"},
19 {429, `{"error":{"code":"insufficient_quota"}}`, "insufficient_quota"},
20 } {
21 t.Run(tc.code, func(t *testing.T) {
22 calls := 0
23 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
24 calls++
25 w.WriteHeader(tc.status)
26 _, _ = w.Write([]byte(tc.body))
27 }))
28 defer srv.Close()
29 for _, managed := range []bool{false, true} {
30 ctx := context.Background()
31 if managed {
32 ctx = WithManagedRecovery(ctx)
33 }
34 _, err := SendWithRetry(ctx, srv.Client(), SendOptions{Provider: "fixture", KeyPresent: true, RetryAuth: true}, func(ctx context.Context) (*http.Request, error) {
35 return http.NewRequestWithContext(ctx, http.MethodPost, srv.URL, nil)
36 })
37 var quota *QuotaError
38 var auth *AuthError
39 if !errors.As(err, &quota) || errors.As(err, &auth) || quota.Status != tc.status || quota.Code != tc.code {
40 t.Fatalf("wrong classification: %v", err)
41 }
42 if strings.Contains(err.Error(), "private-billing") || strings.Contains(err.Error(), "invalid") {
43 t.Fatal("leaked private URL or misdiagnosed credentials")
44 }
45 f := ClassifyRecovery(err)
46 if f.Phase != "quota" || f.Retryable {
47 t.Fatalf("recovery=%+v", f)
48 }
49 }
50 if calls != 2 {
51 t.Fatalf("attempts=%d want one per call", calls)
52 }
53 })
54 }
55 if AsQuotaError(&AuthError{Status: 401, Body: `{"error":{"type":"AuthError","message":"Missing API key."}}`}) != nil {
56 t.Fatal("auth classified as quota")
57 }
58 if ClassifyRecovery(&APIError{Status: 503, Body: `{"error":{"message":"billing service temporarily unavailable"}}`}).Phase == "quota" {
59 t.Fatal("temporary billing service error treated as exhausted quota")
60 }
61 }
62
62 lines GO