veza/veza-backend-api/internal/recovery/error_recovery_test.go

180 lines
4.2 KiB
Go

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)
// Utiliser une erreur qui est détectée comme retryable par IsRetryableError
testErr := errors.New("timeout error")
assert.True(t, composite.CanRecover(testErr))
err := composite.Recover(ctx, testErr)
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)
})
}
}