234 lines
5.5 KiB
Go
234 lines
5.5 KiB
Go
package recovery
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func TestRetry_Success(t *testing.T) {
|
|
ctx := context.Background()
|
|
attempts := 0
|
|
|
|
err := Retry(ctx, func() error {
|
|
attempts++
|
|
return nil
|
|
}, nil)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, attempts)
|
|
}
|
|
|
|
func TestRetry_SuccessAfterRetries(t *testing.T) {
|
|
ctx := context.Background()
|
|
attempts := 0
|
|
|
|
config := &RetryConfig{
|
|
MaxAttempts: 3,
|
|
InitialDelay: 10 * time.Millisecond,
|
|
MaxDelay: 100 * time.Millisecond,
|
|
Multiplier: 2.0,
|
|
}
|
|
|
|
err := Retry(ctx, func() error {
|
|
attempts++
|
|
if attempts < 3 {
|
|
return errors.New("temporary error")
|
|
}
|
|
return nil
|
|
}, config)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 3, attempts)
|
|
}
|
|
|
|
func TestRetry_MaxAttemptsReached(t *testing.T) {
|
|
ctx := context.Background()
|
|
attempts := 0
|
|
|
|
config := &RetryConfig{
|
|
MaxAttempts: 3,
|
|
InitialDelay: 10 * time.Millisecond,
|
|
MaxDelay: 100 * time.Millisecond,
|
|
}
|
|
|
|
err := Retry(ctx, func() error {
|
|
attempts++
|
|
return errors.New("persistent error")
|
|
}, config)
|
|
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "max attempts")
|
|
assert.Equal(t, 3, attempts)
|
|
}
|
|
|
|
func TestRetry_ContextCancellation(t *testing.T) {
|
|
// Utiliser un contexte avec timeout pour garantir l'annulation pendant le délai d'attente
|
|
// Le timeout doit être suffisant pour que fn() soit appelé au moins une fois,
|
|
// mais pas trop long pour que le contexte soit annulé pendant le délai d'attente
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Millisecond)
|
|
defer cancel()
|
|
|
|
attempts := 0
|
|
|
|
config := &RetryConfig{
|
|
MaxAttempts: 5, // Réduire le nombre de tentatives pour garantir que le contexte soit annulé à temps
|
|
InitialDelay: 200 * time.Millisecond, // Délai plus long que le timeout du contexte pour garantir l'annulation
|
|
RetryableFunc: func(err error) bool {
|
|
return true // Toujours retryable pour ce test
|
|
},
|
|
}
|
|
|
|
err := Retry(ctx, func() error {
|
|
attempts++
|
|
// Ajouter un petit délai pour ralentir le test et garantir que le contexte soit annulé pendant l'attente
|
|
time.Sleep(5 * time.Millisecond)
|
|
return errors.New("temporary error")
|
|
}, config)
|
|
|
|
assert.Error(t, err)
|
|
// L'erreur peut être "context cancelled" ou "context cancelled during retry"
|
|
assert.True(t,
|
|
strings.Contains(err.Error(), "context cancelled") ||
|
|
strings.Contains(err.Error(), "context cancelled during retry"),
|
|
"Error should contain 'context cancelled': %s", err.Error())
|
|
assert.Greater(t, attempts, 0) // Devrait avoir fait au moins un appel
|
|
}
|
|
|
|
func TestRetry_NonRetryableError(t *testing.T) {
|
|
ctx := context.Background()
|
|
attempts := 0
|
|
|
|
permanentErr := errors.New("permanent error")
|
|
config := &RetryConfig{
|
|
MaxAttempts: 3,
|
|
RetryableFunc: func(err error) bool {
|
|
return err != permanentErr
|
|
},
|
|
}
|
|
|
|
err := Retry(ctx, func() error {
|
|
attempts++
|
|
return permanentErr
|
|
}, config)
|
|
|
|
assert.Error(t, err)
|
|
assert.Equal(t, 1, attempts) // Ne devrait retryer qu'une fois
|
|
}
|
|
|
|
func TestRetryWithResult_Success(t *testing.T) {
|
|
ctx := context.Background()
|
|
attempts := 0
|
|
|
|
result, err := RetryWithResult(ctx, func() (int, error) {
|
|
attempts++
|
|
return 42, nil
|
|
}, nil)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 42, result)
|
|
assert.Equal(t, 1, attempts)
|
|
}
|
|
|
|
func TestRetryWithResult_Retry(t *testing.T) {
|
|
ctx := context.Background()
|
|
attempts := 0
|
|
|
|
config := &RetryConfig{
|
|
MaxAttempts: 3,
|
|
InitialDelay: 10 * time.Millisecond,
|
|
}
|
|
|
|
result, err := RetryWithResult(ctx, func() (int, error) {
|
|
attempts++
|
|
if attempts < 2 {
|
|
return 0, errors.New("temporary error")
|
|
}
|
|
return 100, nil
|
|
}, config)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 100, result)
|
|
assert.Equal(t, 2, attempts)
|
|
}
|
|
|
|
func TestRetryWithLogger(t *testing.T) {
|
|
ctx := context.Background()
|
|
logger, _ := zap.NewDevelopment()
|
|
attempts := 0
|
|
|
|
config := &RetryConfig{
|
|
MaxAttempts: 3,
|
|
InitialDelay: 10 * time.Millisecond,
|
|
}
|
|
|
|
err := RetryWithLogger(ctx, logger, func() error {
|
|
attempts++
|
|
if attempts < 2 {
|
|
return errors.New("temporary error")
|
|
}
|
|
return nil
|
|
}, config)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 2, attempts)
|
|
}
|
|
|
|
func TestIsRetryableError(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
err error
|
|
expected bool
|
|
}{
|
|
{"timeout", errors.New("timeout"), true},
|
|
{"connection refused", errors.New("connection refused"), true},
|
|
{"server error", errors.New("server error 500"), true},
|
|
{"permanent", errors.New("invalid input"), false},
|
|
{"nil", nil, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := IsRetryableError(tt.err)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCalculateDelay(t *testing.T) {
|
|
config := &RetryConfig{
|
|
InitialDelay: 100 * time.Millisecond,
|
|
MaxDelay: 1 * time.Second,
|
|
Multiplier: 2.0,
|
|
Jitter: false,
|
|
}
|
|
|
|
// Test exponential backoff
|
|
delay1 := calculateDelay(1, config)
|
|
assert.Equal(t, 100*time.Millisecond, delay1)
|
|
|
|
delay2 := calculateDelay(2, config)
|
|
assert.Equal(t, 200*time.Millisecond, delay2)
|
|
|
|
delay3 := calculateDelay(3, config)
|
|
assert.Equal(t, 400*time.Millisecond, delay3)
|
|
|
|
// Test max delay
|
|
delay10 := calculateDelay(10, config)
|
|
assert.LessOrEqual(t, delay10, config.MaxDelay)
|
|
}
|
|
|
|
func TestDefaultRetryConfig(t *testing.T) {
|
|
config := DefaultRetryConfig()
|
|
assert.NotNil(t, config)
|
|
assert.Equal(t, 3, config.MaxAttempts)
|
|
assert.Equal(t, 100*time.Millisecond, config.InitialDelay)
|
|
assert.Equal(t, 5*time.Second, config.MaxDelay)
|
|
assert.Equal(t, 2.0, config.Multiplier)
|
|
assert.True(t, config.Jitter)
|
|
}
|