180 lines
4.2 KiB
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)
|
|
})
|
|
}
|
|
}
|