100 lines
2.8 KiB
Go
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)
|
|
}
|
|
}
|