package recovery import ( "context" "errors" "testing" "time" "github.com/stretchr/testify/assert" "go.uber.org/zap" ) func TestRetryRecoveryStrategy(t *testing.T) { ctx := context.Background() logger, _ := zap.NewDevelopment() attempts := 0 fn := func() error { attempts++ if attempts < 2 { return errors.New("temporary error") } return nil } config := &RetryConfig{ MaxAttempts: 3, InitialDelay: 10 * time.Millisecond, } strategy := NewRetryRecoveryStrategy(fn, config, logger) assert.True(t, strategy.CanRecover(errors.New("timeout"))) assert.False(t, strategy.CanRecover(nil)) err := strategy.Recover(ctx, errors.New("temporary error")) assert.NoError(t, err) assert.Equal(t, 2, attempts) } func TestFallbackRecoveryStrategy(t *testing.T) { ctx := context.Background() logger, _ := zap.NewDevelopment() fallbackCalled := false fallbackFn := func() error { fallbackCalled = true return nil } strategy := NewFallbackRecoveryStrategy(fallbackFn, logger) assert.True(t, strategy.CanRecover(errors.New("any error"))) err := strategy.Recover(ctx, errors.New("original error")) assert.NoError(t, err) assert.True(t, fallbackCalled) } func TestCircuitBreakerRecoveryStrategy(t *testing.T) { // Utiliser un contexte avec timeout court pour éviter d'attendre 30s ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel() logger, _ := zap.NewDevelopment() circuitOpen := true checkFn := func() bool { return circuitOpen } strategy := NewCircuitBreakerRecoveryStrategy(checkFn, logger) assert.True(t, strategy.CanRecover(errors.New("circuit breaker error"))) // Simuler la fermeture du circuit breaker après un court délai go func() { time.Sleep(50 * time.Millisecond) circuitOpen = false }() err := strategy.Recover(ctx, errors.New("circuit breaker open")) // Le timeout du contexte devrait se produire avant le timeout de la stratégie assert.Error(t, err) assert.Contains(t, err.Error(), "timeout") } func TestCompositeRecoveryStrategy(t *testing.T) { ctx := context.Background() logger, _ := zap.NewDevelopment() attempts := 0 retryFn := func() error { attempts++ if attempts < 2 { return errors.New("temporary error") } return nil } config := &RetryConfig{ MaxAttempts: 3, InitialDelay: 10 * time.Millisecond, RetryableFunc: func(err error) bool { return true // Toujours retryable pour ce test }, } retryStrategy := NewRetryRecoveryStrategy(retryFn, config, logger) composite := NewCompositeRecoveryStrategy([]ErrorRecoveryStrategy{retryStrategy}, logger) assert.True(t, composite.CanRecover(errors.New("timeout"))) err := composite.Recover(ctx, errors.New("temporary error")) assert.NoError(t, err) assert.Equal(t, 2, attempts) } func TestRecoverWithStrategies(t *testing.T) { ctx := context.Background() logger, _ := zap.NewDevelopment() fallbackCalled := false retryFn := func() error { return errors.New("retry failed") } retryStrategy := NewRetryRecoveryStrategy(retryFn, nil, logger) fallbackStrategy := NewFallbackRecoveryStrategy(func() error { fallbackCalled = true return nil }, logger) strategies := []ErrorRecoveryStrategy{retryStrategy, fallbackStrategy} err := RecoverWithStrategies(ctx, errors.New("original error"), strategies, logger) assert.NoError(t, err) assert.True(t, fallbackCalled) } func TestIsTemporaryError(t *testing.T) { tests := []struct { name string err error expected bool }{ {"timeout", errors.New("timeout"), true}, {"connection refused", errors.New("connection refused"), true}, {"permanent", errors.New("invalid input"), false}, {"nil", nil, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := IsTemporaryError(tt.err) assert.Equal(t, tt.expected, result) }) } } func TestIsPermanentError(t *testing.T) { tests := []struct { name string err error expected bool }{ {"timeout", errors.New("timeout"), false}, {"permanent", errors.New("invalid input"), true}, {"nil", nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := IsPermanentError(tt.err) assert.Equal(t, tt.expected, result) }) } }