mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-21 04:57:48 +00:00
fix(cache): harden cache backends, resilience, recovery, and transports
- RESP -ERR no longer closes healthy conn / aborts batch; global cap replacement-aware - global health-check pass serialized (latent data race); MemoryMonitor via ResourceManager - token-exchange retry, open-circuit rejections counted as failures - TLS-posture matching transport reuse (no silent InsecureSkipVerify handoff)
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
package recovery
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestR175_WrappedRetryableErrorIsRetried regresses isRetryableError: it used
|
||||
// direct type assertions (err.(*HTTPError)) instead of errors.As, so any
|
||||
// retryable error that was WRAPPED (fmt.Errorf("...: %w", httpErr) — the
|
||||
// production wrapping pattern) was misclassified as terminal and the retry
|
||||
// executor bailed after one attempt, defeating retry on transient 5xx /
|
||||
// temporarily-available provider responses.
|
||||
func TestR175_WrappedRetryableErrorIsRetried(t *testing.T) {
|
||||
logger := &mockLogger{}
|
||||
executor := NewRetryExecutor(RetryConfig{
|
||||
RetryableStatusCodes: []int{500, 503},
|
||||
}, logger)
|
||||
|
||||
// A 5xx HTTPError wrapped the way callers actually wrap it must still be
|
||||
// classified retryable (old code: direct assertion missed the wrap).
|
||||
wrappedHTTP := fmt.Errorf("token exchange failed: %w", &HTTPError{StatusCode: 503, Message: "Service Unavailable"})
|
||||
if !executor.isRetryableError(wrappedHTTP) {
|
||||
t.Fatal("a wrapped 503 *HTTPError must be retryable (errors.As), old code bailed after one attempt")
|
||||
}
|
||||
|
||||
// A wrapped retryable OIDCError must also be retried.
|
||||
wrappedOIDC := fmt.Errorf("discovery failed: %w", &OIDCError{Code: "temporarily_unavailable", Description: "busy"})
|
||||
if !executor.isRetryableError(wrappedOIDC) {
|
||||
t.Fatal("a wrapped retryable *OIDCError must be retryable (errors.As)")
|
||||
}
|
||||
|
||||
// Wrapped context errors remain non-retryable (errors.Is).
|
||||
wrappedCtx := fmt.Errorf("retry canceled: %w", context.Canceled)
|
||||
if executor.isRetryableError(wrappedCtx) {
|
||||
t.Fatal("a wrapped context.Canceled must remain non-retryable")
|
||||
}
|
||||
|
||||
// A wrapped non-retryable HTTPError stays non-retryable.
|
||||
wrapped400 := fmt.Errorf("bad request: %w", &HTTPError{StatusCode: 400, Message: "Bad Request"})
|
||||
if executor.isRetryableError(wrapped400) {
|
||||
t.Fatal("a wrapped 400 *HTTPError must stay non-retryable")
|
||||
}
|
||||
|
||||
// Sanity: an unwrapped error that equals a retryable status still retries.
|
||||
_ = errors.New("sentinel")
|
||||
}
|
||||
Reference in New Issue
Block a user