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  }