mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-12 04:04:26 +00:00
- fail-on-old regression tests for every shipped fix (R-rounds) - fuzz targets (parseJWT, bearer), concurrency-stress, shuffle-flake guards - coverage-closing contract tests (health-check backend, TLS-posture reuse) - r154 ID-token fixture built from parts to avoid false-positive secret scan
87 lines
3.0 KiB
Go
87 lines
3.0 KiB
Go
package traefikoidc
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestExecuteWithRecovery_RetryCountsOnce guards the R144 fix in
|
|
// error_recovery.go ExecuteWithRecovery: the circuit breaker was nested
|
|
// INSIDE the retry loop, so each retry attempt recorded a separate
|
|
// failure — a single flaky-but-retryable call with two attempts opened
|
|
// the circuit (2 failures) in one logical operation. The correct
|
|
// compositing (matching TokenResilienceManager's R106 rule) is retry
|
|
// inner, circuit outer: one logical call must count as ONE circuit
|
|
// failure regardless of internal retries.
|
|
func TestExecuteWithRecovery_RetryCountsOnce(t *testing.T) {
|
|
logger := newNoOpLogger()
|
|
mgr := &ErrorRecoveryManager{
|
|
circuitBreakers: map[string]*CircuitBreaker{
|
|
"svc": NewCircuitBreaker(CircuitBreakerConfig{
|
|
MaxFailures: 2,
|
|
Timeout: time.Second,
|
|
ResetTimeout: time.Second,
|
|
}, logger),
|
|
},
|
|
retryExecutor: NewRetryExecutor(RetryConfig{
|
|
MaxAttempts: 2,
|
|
InitialDelay: time.Millisecond,
|
|
MaxDelay: time.Millisecond,
|
|
BackoffFactor: 1,
|
|
EnableJitter: false,
|
|
RetryableErrors: []string{"boom"},
|
|
}, logger),
|
|
gracefulDegradation: NewGracefulDegradation(DefaultGracefulDegradationConfig(), logger),
|
|
logger: logger,
|
|
}
|
|
|
|
_ = mgr.ExecuteWithRecovery(context.Background(), "svc", func() error {
|
|
return errors.New("boom")
|
|
})
|
|
|
|
cb := mgr.circuitBreakers["svc"]
|
|
if got := cb.failures; got != 1 {
|
|
t.Fatalf("one logical call with an internal retry must record exactly one circuit failure, got %d (circuit opened prematurely per retry attempt)", got)
|
|
}
|
|
}
|
|
|
|
// TestCookiePath_StripsInboundAuthorization guards the R144 fix in
|
|
// middleware.go forwardAuthorized: the raw inbound Authorization header
|
|
// was only stripped on the bearer path, while the cookie/session path
|
|
// forwarded a client-supplied Authorization verbatim. A session user
|
|
// could therefore present an arbitrary — possibly more privileged —
|
|
// bearer that a downstream verifier might trust over X-Forwarded-User,
|
|
// or leak a raw token into downstream logs. The header must be stripped
|
|
// for the session path too when stripAuthorizationHeader is set.
|
|
func TestCookiePath_StripsInboundAuthorization(t *testing.T) {
|
|
var captured http.Header
|
|
oidc := &TraefikOidc{
|
|
next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
captured = r.Header.Clone()
|
|
w.WriteHeader(http.StatusOK)
|
|
}),
|
|
logger: newNoOpLogger(),
|
|
minimalHeaders: true,
|
|
stripAuthorizationHeader: true,
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/app", nil)
|
|
req.Header.Set("Authorization", "Bearer unrelated-downstream-token")
|
|
rw := httptest.NewRecorder()
|
|
|
|
p := &principal{
|
|
Source: sourceSession,
|
|
Identifier: "[email protected]",
|
|
}
|
|
|
|
oidc.forwardAuthorized(rw, req, p)
|
|
|
|
if v := captured.Get("Authorization"); v != "" {
|
|
t.Fatalf("cookie/session path must strip the inbound Authorization header, but downstream received %q", v)
|
|
}
|
|
}
|