From e60738b084a2a8c9a3e442a3946f6d95a121d828 Mon Sep 17 00:00:00 2001 From: "CJACK." Date: Thu, 2 Apr 2026 12:58:09 +0800 Subject: [PATCH] auth: preserve ensure error after retry exhaustion --- internal/auth/request.go | 8 ++++++++ internal/auth/request_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/internal/auth/request.go b/internal/auth/request.go index 9a147d2..e6a0d88 100644 --- a/internal/auth/request.go +++ b/internal/auth/request.go @@ -79,12 +79,19 @@ func (r *Resolver) Determine(req *http.Request) (*RequestAuth, error) { func (r *Resolver) acquireManagedRequestAuth(ctx context.Context, callerID, target string) (*RequestAuth, error) { tried := map[string]bool{} + var lastEnsureErr error for { if target == "" && len(tried) >= len(r.Store.Accounts()) { + if lastEnsureErr != nil { + return nil, lastEnsureErr + } return nil, ErrNoAccount } acc, ok := r.Pool.AcquireWait(ctx, target, tried) if !ok { + if lastEnsureErr != nil { + return nil, lastEnsureErr + } return nil, ErrNoAccount } @@ -98,6 +105,7 @@ func (r *Resolver) acquireManagedRequestAuth(ctx context.Context, callerID, targ } if err := r.ensureManagedToken(ctx, a); err != nil { + lastEnsureErr = err tried[a.AccountID] = true r.Pool.Release(a.AccountID) if target != "" { diff --git a/internal/auth/request_test.go b/internal/auth/request_test.go index d8f36b3..edf163d 100644 --- a/internal/auth/request_test.go +++ b/internal/auth/request_test.go @@ -365,3 +365,33 @@ func TestDetermineTargetAccountDoesNotFallbackOnLoginFailure(t *testing.T) { t.Fatal("expected determine to fail for broken target account") } } + +func TestDetermineManagedAccountReturnsLastEnsureErrorWhenAllFail(t *testing.T) { + t.Setenv("DS2API_CONFIG_JSON", `{ + "keys":["managed-key"], + "accounts":[ + {"email":"bad1@example.com","password":"pwd"}, + {"email":"bad2@example.com","password":"pwd"} + ] + }`) + store := config.LoadStore() + pool := account.NewPool(store) + ensureErr := errors.New("all credentials stale") + resolver := NewResolver(store, pool, func(_ context.Context, _ config.Account) (string, error) { + return "", ensureErr + }) + + req, _ := http.NewRequest(http.MethodPost, "/v1/chat/completions", nil) + req.Header.Set("x-api-key", "managed-key") + + _, err := resolver.Determine(req) + if err == nil { + t.Fatal("expected determine to fail") + } + if !errors.Is(err, ensureErr) { + t.Fatalf("expected ensure error, got %v", err) + } + if errors.Is(err, ErrNoAccount) { + t.Fatalf("expected auth-style ensure error, got ErrNoAccount") + } +}