veza/veza-backend-api/internal/recovery/retry_test.go
senke 286be8ba1d
Some checks failed
Backend API CI / test-unit (push) Failing after 0s
Backend API CI / test-integration (push) Failing after 0s
Frontend CI / test (push) Failing after 0s
Storybook Audit / Build & audit Storybook (push) Failing after 0s
chore(v0.102): consolidate remaining changes — docs, frontend, backend
- docs: SCOPE_CONTROL, CONTRIBUTING, README, .github templates
- frontend: DeveloperDashboardView, Player components, MSW handlers, auth, reactQuerySync
- backend: playback_analytics, playlist_service, testutils, integration README

Excluded (artifacts): .auth, playwright-report, test-results, storybook_audit_detailed.json
2026-02-20 13:02:12 +01:00

232 lines
4.9 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) {
ctx, cancel := context.WithCancel(context.Background())
attempts := 0
config := &RetryConfig{
MaxAttempts: 5,
InitialDelay: 200 * time.Millisecond,
RetryableFunc: func(err error) bool {
return true
},
OnRetry: func(attempt int, err error) {
// Annuler le contexte dès le premier retry pour garantir l'annulation pendant le délai
if attempt == 1 {
cancel()
}
},
}
err := Retry(ctx, func() error {
attempts++
return errors.New("temporary error")
}, config)
assert.Error(t, err)
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)
}
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)
}