Files
traefik-selectel/internal/httpx/retry_test.go
T
vasyanskandClaude Sonnet 5 273cd54415
Build / Tests (push) Successful in 13s
Build / Build image (push) Failing after 15s
Build / Notify on failure (push) Failing after 0s
feat: traefik-selectel-dns — A-записи Selectel по роутам Traefik + CI сборка образа
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 21:50:07 +07:00

100 lines
2.8 KiB
Go

package httpx
import (
"context"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
"time"
)
func noSleep(context.Context, time.Duration) error { return nil }
func TestRetrierRetriesOn5xxAnd429(t *testing.T) {
var calls int32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch atomic.AddInt32(&calls, 1) {
case 1:
w.WriteHeader(http.StatusTooManyRequests)
case 2:
w.WriteHeader(http.StatusBadGateway)
default:
w.WriteHeader(http.StatusOK)
}
}))
defer srv.Close()
r := Retrier{MaxAttempts: 4, BaseDelay: time.Millisecond, Sleep: noSleep}
resp, err := r.Do(context.Background(), srv.Client(), func() (*http.Request, error) {
return http.NewRequest(http.MethodGet, srv.URL, nil)
})
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 || calls != 3 {
t.Fatalf("status=%d calls=%d", resp.StatusCode, calls)
}
}
func TestRetrierNoRetryOn4xx(t *testing.T) {
var calls int32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt32(&calls, 1)
w.WriteHeader(http.StatusNotFound)
}))
defer srv.Close()
r := Retrier{MaxAttempts: 4, BaseDelay: time.Millisecond, Sleep: noSleep}
resp, err := r.Do(context.Background(), srv.Client(), func() (*http.Request, error) {
return http.NewRequest(http.MethodGet, srv.URL, nil)
})
if err != nil {
t.Fatal(err)
}
resp.Body.Close()
if calls != 1 || resp.StatusCode != 404 {
t.Fatalf("calls=%d status=%d", calls, resp.StatusCode)
}
}
func TestRetrierExhaustedReturnsLastResponse(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusServiceUnavailable)
}))
defer srv.Close()
r := Retrier{MaxAttempts: 2, BaseDelay: time.Millisecond, Sleep: noSleep}
resp, err := r.Do(context.Background(), srv.Client(), func() (*http.Request, error) {
return http.NewRequest(http.MethodGet, srv.URL, nil)
})
if err != nil {
t.Fatal(err)
}
resp.Body.Close()
if resp.StatusCode != 503 {
t.Fatalf("status=%d", resp.StatusCode)
}
}
func TestRetrierNetworkError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
url := srv.URL
srv.Close()
r := Retrier{MaxAttempts: 3, BaseDelay: time.Millisecond, Sleep: noSleep}
_, err := r.Do(context.Background(), http.DefaultClient, func() (*http.Request, error) {
return http.NewRequest(http.MethodGet, url, nil)
})
if err == nil {
t.Fatal("ожидалась ошибка")
}
}
func TestBackoffCapped(t *testing.T) {
r := Retrier{BaseDelay: time.Second, MaxDelay: 5 * time.Second}
if got := r.backoff(1); got != time.Second {
t.Errorf("attempt1 = %v", got)
}
if got := r.backoff(10); got != 5*time.Second {
t.Errorf("attempt10 = %v", got)
}
}