feat: phase 8 serviceadapter crd rollout (32/33 tasks)
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
package resilience
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRetryOnSuccess(t *testing.T) {
|
||||
cfg := &RetryConfig{MaxAttempts: 3}
|
||||
attempts := 0
|
||||
|
||||
resp, err := DoRetry(context.Background(), cfg, func(ctx context.Context, attempt int) (*http.Response, error) {
|
||||
attempts++
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: io.NopCloser(strings.NewReader("ok")),
|
||||
}, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if attempts != 1 {
|
||||
t.Errorf("expected 1 attempt on success, got %d", attempts)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
t.Errorf("expected status 200, got %d", resp.StatusCode)
|
||||
}
|
||||
resp.Body.Close()
|
||||
}
|
||||
|
||||
func TestRetryOn5xx(t *testing.T) {
|
||||
cfg := &RetryConfig{
|
||||
MaxAttempts: 3,
|
||||
InitialBackoff: 10 * time.Millisecond,
|
||||
MaxBackoff: 50 * time.Millisecond,
|
||||
BackoffMultiplier: 2.0,
|
||||
}
|
||||
attempts := 0
|
||||
|
||||
resp, err := DoRetry(context.Background(), cfg, func(ctx context.Context, attempt int) (*http.Response, error) {
|
||||
attempts++
|
||||
if attempt < 2 {
|
||||
// First two attempts return 503
|
||||
return &http.Response{
|
||||
StatusCode: 503,
|
||||
Body: io.NopCloser(strings.NewReader("unavailable")),
|
||||
}, nil
|
||||
}
|
||||
// Third attempt succeeds
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: io.NopCloser(strings.NewReader("ok")),
|
||||
}, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if attempts != 3 {
|
||||
t.Errorf("expected 3 attempts (2 retries), got %d", attempts)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
t.Errorf("expected status 200, got %d", resp.StatusCode)
|
||||
}
|
||||
resp.Body.Close()
|
||||
}
|
||||
|
||||
func TestRetryExhaustion(t *testing.T) {
|
||||
cfg := &RetryConfig{
|
||||
MaxAttempts: 2,
|
||||
InitialBackoff: 10 * time.Millisecond,
|
||||
MaxBackoff: 50 * time.Millisecond,
|
||||
}
|
||||
attempts := 0
|
||||
|
||||
resp, err := DoRetry(context.Background(), cfg, func(ctx context.Context, attempt int) (*http.Response, error) {
|
||||
attempts++
|
||||
// Always return 503
|
||||
return &http.Response{
|
||||
StatusCode: 503,
|
||||
Body: io.NopCloser(strings.NewReader("unavailable")),
|
||||
}, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if attempts != 2 {
|
||||
t.Errorf("expected 2 attempts (max), got %d", attempts)
|
||||
}
|
||||
if resp.StatusCode != 503 {
|
||||
t.Errorf("expected status 503, got %d", resp.StatusCode)
|
||||
}
|
||||
resp.Body.Close()
|
||||
}
|
||||
|
||||
func TestRetryWithContext(t *testing.T) {
|
||||
cfg := &RetryConfig{MaxAttempts: 10}
|
||||
attempts := 0
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Cancel after a short delay
|
||||
go func() {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
cancel()
|
||||
}()
|
||||
|
||||
resp, err := DoRetry(ctx, cfg, func(ctx context.Context, attempt int) (*http.Response, error) {
|
||||
attempts++
|
||||
time.Sleep(30 * time.Millisecond)
|
||||
return &http.Response{
|
||||
StatusCode: 503,
|
||||
Body: io.NopCloser(strings.NewReader("unavailable")),
|
||||
}, nil
|
||||
})
|
||||
|
||||
if err != context.Canceled {
|
||||
t.Errorf("expected context.Canceled error, got: %v", err)
|
||||
}
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
// Should have fewer than all attempts due to cancellation
|
||||
if attempts >= 10 {
|
||||
t.Errorf("expected fewer than 10 attempts due to cancellation, got %d", attempts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetryPolicyShouldRetry(t *testing.T) {
|
||||
policy := &RetryPolicy{Retryable: true}
|
||||
|
||||
resp503 := &http.Response{StatusCode: 503}
|
||||
if !policy.ShouldRetry(resp503) {
|
||||
t.Errorf("expected to retry on 503")
|
||||
}
|
||||
|
||||
resp200 := &http.Response{StatusCode: 200}
|
||||
if policy.ShouldRetry(resp200) {
|
||||
t.Errorf("expected not to retry on 200")
|
||||
}
|
||||
|
||||
resp404 := &http.Response{StatusCode: 404}
|
||||
if policy.ShouldRetry(resp404) {
|
||||
t.Errorf("expected not to retry on 404")
|
||||
}
|
||||
|
||||
policyNoRetry := &RetryPolicy{Retryable: false}
|
||||
if policyNoRetry.ShouldRetry(resp503) {
|
||||
t.Errorf("expected not to retry when retryable=false")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user