github.com/go-playground/pkg/v5@v5.29.1/net/http/retrier_test.go (about) 1 //go:build go1.18 2 // +build go1.18 3 4 package httpext 5 6 import ( 7 "context" 8 "errors" 9 "net/http" 10 "net/http/httptest" 11 "testing" 12 13 . "github.com/go-playground/assert/v2" 14 errorsext "github.com/go-playground/pkg/v5/errors" 15 . "github.com/go-playground/pkg/v5/values/result" 16 ) 17 18 func TestRetryer_SuccessNoRetries(t *testing.T) { 19 ctx := context.Background() 20 21 type Test struct { 22 Name string 23 } 24 tst := Test{Name: "test"} 25 26 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 27 _ = JSON(w, http.StatusOK, tst) 28 })) 29 defer server.Close() 30 31 retryer := NewRetryer() 32 33 result := retryer.DoResponse(ctx, func(ctx context.Context) Result[*http.Request, error] { 34 req, err := http.NewRequestWithContext(ctx, "GET", server.URL, nil) 35 if err != nil { 36 return Err[*http.Request, error](err) 37 } 38 return Ok[*http.Request, error](req) 39 }, http.StatusOK) 40 Equal(t, result.IsOk(), true) 41 Equal(t, result.Unwrap().StatusCode, http.StatusOK) 42 defer result.Unwrap().Body.Close() 43 44 var responseResult Test 45 err := retryer.Do(ctx, func(ctx context.Context) Result[*http.Request, error] { 46 req, err := http.NewRequestWithContext(ctx, "GET", server.URL, nil) 47 if err != nil { 48 return Err[*http.Request, error](err) 49 } 50 return Ok[*http.Request, error](req) 51 }, &responseResult, http.StatusOK) 52 Equal(t, err, nil) 53 Equal(t, responseResult, tst) 54 } 55 56 func TestRetryer_SuccessWithRetries(t *testing.T) { 57 ctx := context.Background() 58 var count int 59 60 type Test struct { 61 Name string 62 } 63 tst := Test{Name: "test"} 64 65 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 66 if count < 2 { 67 w.WriteHeader(http.StatusServiceUnavailable) 68 count++ 69 return 70 } 71 _ = JSON(w, http.StatusOK, tst) 72 })) 73 defer server.Close() 74 75 retryer := NewRetryer().Backoff(nil) 76 77 result := retryer.DoResponse(ctx, func(ctx context.Context) Result[*http.Request, error] { 78 req, err := http.NewRequestWithContext(ctx, "GET", server.URL, nil) 79 if err != nil { 80 return Err[*http.Request, error](err) 81 } 82 return Ok[*http.Request, error](req) 83 }, http.StatusOK) 84 Equal(t, result.IsOk(), true) 85 Equal(t, result.Unwrap().StatusCode, http.StatusOK) 86 defer result.Unwrap().Body.Close() 87 88 count = 0 // reset count 89 90 var responseResult Test 91 err := retryer.Do(ctx, func(ctx context.Context) Result[*http.Request, error] { 92 req, err := http.NewRequestWithContext(ctx, "GET", server.URL, nil) 93 if err != nil { 94 return Err[*http.Request, error](err) 95 } 96 return Ok[*http.Request, error](req) 97 }, &responseResult, http.StatusOK) 98 Equal(t, err, nil) 99 Equal(t, responseResult, tst) 100 } 101 102 func TestRetryer_FailureMaxRetries(t *testing.T) { 103 ctx := context.Background() 104 105 type Test struct { 106 Name string 107 } 108 109 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 110 w.WriteHeader(http.StatusInternalServerError) 111 })) 112 defer server.Close() 113 114 retryer := NewRetryer().Backoff(nil).MaxAttempts(errorsext.MaxAttempts, 2) 115 116 result := retryer.DoResponse(ctx, func(ctx context.Context) Result[*http.Request, error] { 117 req, err := http.NewRequestWithContext(ctx, "GET", server.URL, nil) 118 if err != nil { 119 return Err[*http.Request, error](err) 120 } 121 return Ok[*http.Request, error](req) 122 }, http.StatusOK) 123 Equal(t, result.IsErr(), true) 124 125 var responseResult Test 126 err := retryer.Do(ctx, func(ctx context.Context) Result[*http.Request, error] { 127 req, err := http.NewRequestWithContext(ctx, "GET", server.URL, nil) 128 if err != nil { 129 return Err[*http.Request, error](err) 130 } 131 return Ok[*http.Request, error](req) 132 }, &responseResult, http.StatusOK) 133 NotEqual(t, err, nil) 134 } 135 136 func TestRetryer_ExtractStatusBody(t *testing.T) { 137 ctx := context.Background() 138 eStr := "nooooooooooooo!" 139 140 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 141 w.WriteHeader(http.StatusInternalServerError) 142 _, _ = w.Write([]byte(eStr)) 143 })) 144 defer server.Close() 145 146 retryer := NewRetryer().MaxAttempts(errorsext.MaxAttempts, 3) 147 148 result := retryer.DoResponse(ctx, func(ctx context.Context) Result[*http.Request, error] { 149 req, err := http.NewRequestWithContext(ctx, "GET", server.URL, nil) 150 if err != nil { 151 return Err[*http.Request, error](err) 152 } 153 return Ok[*http.Request, error](req) 154 }, http.StatusOK) 155 Equal(t, result.IsErr(), true) 156 var esc ErrStatusCode 157 Equal(t, errors.As(result.Err(), &esc), true) 158 Equal(t, esc.IsRetryableStatusCode, false) 159 // check the ultimate failed response body is intact 160 Equal(t, string(esc.Body), eStr) 161 } 162 163 func TestRetryer_ExtractStatusBodyEarlyReturn(t *testing.T) { 164 ctx := context.Background() 165 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 166 w.WriteHeader(http.StatusUnauthorized) 167 _, _ = w.Write([]byte(http.StatusText(http.StatusUnauthorized))) 168 })) 169 defer server.Close() 170 171 var count int 172 173 retryer := NewRetryer().Backoff(func(_ context.Context, _ int, _ error) { 174 count++ 175 }).MaxAttempts(errorsext.MaxAttempts, 2) 176 177 result := retryer.DoResponse(ctx, func(ctx context.Context) Result[*http.Request, error] { 178 req, err := http.NewRequestWithContext(ctx, "GET", server.URL, nil) 179 if err != nil { 180 return Err[*http.Request, error](err) 181 } 182 return Ok[*http.Request, error](req) 183 }, http.StatusOK) 184 Equal(t, result.IsErr(), true) 185 var esc ErrStatusCode 186 Equal(t, errors.As(result.Err(), &esc), true) 187 Equal(t, esc.IsRetryableStatusCode, false) 188 // check the ultimate failed response body is intact 189 Equal(t, string(esc.Body), http.StatusText(http.StatusUnauthorized)) 190 Equal(t, count, 0) 191 }