veza/veza-backend-api/internal/recovery/retry_test.go
senke b8adaf8935 [BE-SVC-022] be-svc: Implement data export service
- Created DataExportService for comprehensive user data export (GDPR compliance)
- Exports all user data: profile, settings, tracks, playlists, comments, likes, analytics, federated identities, roles
- Added ExportUserData method to retrieve all user data from database
- Added ExportUserDataAsJSON method to export as downloadable JSON file
- Added endpoint GET /api/v1/users/me/export that returns JSON file download
- Comprehensive unit tests for export service
- Proper error handling and logging

Phase: PHASE-6
Priority: P2
Progress: 118/267 (44.19%)
2025-12-24 18:01:00 +01:00

226 lines
4.7 KiB
Go

package recovery
import (
"context"
"errors"
"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: 10,
InitialDelay: 50 * time.Millisecond,
}
// Annuler le contexte dans une goroutine après un court délai
go func() {
time.Sleep(10 * time.Millisecond)
cancel()
}()
err := Retry(ctx, func() error {
attempts++
return errors.New("temporary error")
}, config)
assert.Error(t, err)
assert.Contains(t, err.Error(), "context cancelled")
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)
}