mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-11-02 16:32:14 +00:00
123 lines
3.5 KiB
Go
123 lines
3.5 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/lib/pq"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func transientDatabaseError(code string) error {
|
|
return &pq.Error{Code: pq.ErrorCode(code), Message: "database is temporarily unavailable"}
|
|
}
|
|
|
|
func TestIsTransientDatabaseInitializationError(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
err error
|
|
want bool
|
|
}{
|
|
{name: "postgres is starting", err: transientDatabaseError("57P03"), want: true},
|
|
{name: "connection failure", err: fmt.Errorf("wrapped: %w", transientDatabaseError("08006")), want: true},
|
|
{name: "authentication failure", err: transientDatabaseError("28P01"), want: false},
|
|
{name: "migration error", err: errors.New("migration checksum mismatch"), want: false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
require.Equal(t, tt.want, isTransientDatabaseInitializationError(tt.err))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestInitializeDatabaseWithRetryEventuallySucceeds(t *testing.T) {
|
|
attempts := 0
|
|
var delays []time.Duration
|
|
err := initializeDatabaseWithRetryWithWait(context.Background(), func(context.Context) error {
|
|
attempts++
|
|
if attempts <= 3 {
|
|
return transientDatabaseError("57P03")
|
|
}
|
|
return nil
|
|
}, func(_ context.Context, delay time.Duration) error {
|
|
delays = append(delays, delay)
|
|
return nil
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, 4, attempts)
|
|
require.Equal(t, []time.Duration{time.Second, 2 * time.Second, 4 * time.Second}, delays)
|
|
}
|
|
|
|
func TestInitializeDatabaseWithRetryFailsFastForPermanentError(t *testing.T) {
|
|
attempts := 0
|
|
waitCalled := false
|
|
permanentErr := transientDatabaseError("28P01")
|
|
err := initializeDatabaseWithRetryWithWait(context.Background(), func(context.Context) error {
|
|
attempts++
|
|
return permanentErr
|
|
}, func(_ context.Context, _ time.Duration) error {
|
|
waitCalled = true
|
|
return nil
|
|
})
|
|
|
|
require.ErrorIs(t, err, permanentErr)
|
|
require.Equal(t, 1, attempts)
|
|
require.False(t, waitCalled)
|
|
}
|
|
|
|
func TestInitializeDatabaseWithRetryStopsWhenContextIsCanceled(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
attempts := 0
|
|
err := initializeDatabaseWithRetryWithWait(ctx, func(context.Context) error {
|
|
attempts++
|
|
return transientDatabaseError("57P03")
|
|
}, func(ctx context.Context, _ time.Duration) error {
|
|
cancel()
|
|
return ctx.Err()
|
|
})
|
|
|
|
require.ErrorIs(t, err, context.Canceled)
|
|
require.Equal(t, 1, attempts)
|
|
}
|
|
|
|
func TestInitializeDatabaseWithRetryReturnsLastErrorAfterLimit(t *testing.T) {
|
|
attempts := 0
|
|
lastErr := transientDatabaseError("08006")
|
|
var delays []time.Duration
|
|
err := initializeDatabaseWithRetryWithWait(context.Background(), func(context.Context) error {
|
|
attempts++
|
|
return lastErr
|
|
}, func(_ context.Context, delay time.Duration) error {
|
|
delays = append(delays, delay)
|
|
return nil
|
|
})
|
|
|
|
require.ErrorIs(t, err, lastErr)
|
|
require.Equal(t, maxDatabaseInitializationRetries+1, attempts)
|
|
require.Len(t, delays, maxDatabaseInitializationRetries)
|
|
require.Equal(t, databaseInitializationRetryMax, delays[len(delays)-1])
|
|
}
|
|
|
|
func TestInitializeDatabaseWithRetryAllowsIdempotentMigrationRetry(t *testing.T) {
|
|
applied := make(map[string]bool)
|
|
attempts := 0
|
|
err := initializeDatabaseWithRetryWithWait(context.Background(), func(context.Context) error {
|
|
attempts++
|
|
if !applied["001_init.sql"] {
|
|
applied["001_init.sql"] = true
|
|
return transientDatabaseError("57P03")
|
|
}
|
|
return nil
|
|
}, func(_ context.Context, _ time.Duration) error { return nil })
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, 2, attempts)
|
|
require.True(t, applied["001_init.sql"])
|
|
}
|