github.com/openshift-online/ocm-sdk-go@v0.1.473/retry_test.go (about)

     1  /*
     2  Copyright (c) 2021 Red Hat, Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8    http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package sdk
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"errors"
    23  	"net/http"
    24  	"time"
    25  
    26  	"github.com/openshift-online/ocm-sdk-go/logging"
    27  
    28  	. "github.com/onsi/ginkgo/v2/dsl/core"             // nolint
    29  	. "github.com/onsi/gomega"                         // nolint
    30  	. "github.com/openshift-online/ocm-sdk-go/testing" // nolint
    31  )
    32  
    33  var _ = Describe("Retry", func() {
    34  	var ctx context.Context
    35  	var token string
    36  
    37  	BeforeEach(func() {
    38  		// Create a context:
    39  		ctx = context.Background()
    40  
    41  		// Create a token:
    42  		token = MakeTokenString("Bearer", 15*time.Minute)
    43  	})
    44  
    45  	Describe("Get", func() {
    46  		It("Retries if protocol error", func() {
    47  			// Create a connection with a transport wrapper that returns an error for
    48  			// the first request and 200 for the second.
    49  			connection, err := NewConnectionBuilder().
    50  				Logger(logger).
    51  				Tokens(token).
    52  				TransportWrapper(func(_ http.RoundTripper) http.RoundTripper {
    53  					return CombineTransports(
    54  						ErrorTransport(errors.New("PROTOCOL_ERROR")),
    55  						JSONTransport(http.StatusOK, "{}"),
    56  					)
    57  				}).
    58  				RetryInterval(10 * time.Millisecond).
    59  				BuildContext(ctx)
    60  			Expect(err).ToNot(HaveOccurred())
    61  
    62  			// Send the request:
    63  			response, err := connection.Get().Path("/mypath").Send()
    64  			Expect(err).ToNot(HaveOccurred())
    65  			Expect(response).ToNot(BeNil())
    66  		})
    67  
    68  		It("Retries for 429", func() {
    69  			// Create a connection with a transport wrapper that returns 429 for the
    70  			// first request and 200 for the second.
    71  			connection, err := NewConnectionBuilder().
    72  				Logger(logger).
    73  				Tokens(token).
    74  				TransportWrapper(func(_ http.RoundTripper) http.RoundTripper {
    75  					return CombineTransports(
    76  						JSONTransport(http.StatusTooManyRequests, "{}"),
    77  						JSONTransport(http.StatusOK, "{}"),
    78  					)
    79  				}).
    80  				RetryInterval(10 * time.Millisecond).
    81  				BuildContext(ctx)
    82  			Expect(err).ToNot(HaveOccurred())
    83  
    84  			// Send the request:
    85  			response, err := connection.Get().Path("/mypath").Send()
    86  			Expect(err).ToNot(HaveOccurred())
    87  			Expect(response).ToNot(BeNil())
    88  		})
    89  
    90  		It("Retries for 503", func() {
    91  			// Create a connection with a transport wrapper that returns 503 for the
    92  			// first request and 200 for the second.
    93  			connection, err := NewConnectionBuilder().
    94  				Logger(logger).
    95  				Tokens(token).
    96  				TransportWrapper(func(_ http.RoundTripper) http.RoundTripper {
    97  					return CombineTransports(
    98  						JSONTransport(http.StatusServiceUnavailable, "{}"),
    99  						JSONTransport(http.StatusOK, "{}"),
   100  					)
   101  				}).
   102  				RetryInterval(10 * time.Millisecond).
   103  				BuildContext(ctx)
   104  			Expect(err).ToNot(HaveOccurred())
   105  
   106  			// Send the request:
   107  			response, err := connection.Get().Path("/mypath").Send()
   108  			Expect(err).ToNot(HaveOccurred())
   109  			Expect(response).ToNot(BeNil())
   110  		})
   111  	})
   112  
   113  	Describe("Delete", func() {
   114  		It("Retries for protocol error", func() {
   115  			// Create a connection with a transport wrapper that returns an error for
   116  			// the first request and 200 for the second.
   117  			connection, err := NewConnectionBuilder().
   118  				Logger(logger).
   119  				Tokens(token).
   120  				TransportWrapper(func(_ http.RoundTripper) http.RoundTripper {
   121  					return CombineTransports(
   122  						ErrorTransport(errors.New("PROTOCOL_ERROR")),
   123  						JSONTransport(http.StatusOK, "{}"),
   124  					)
   125  				}).
   126  				RetryInterval(10 * time.Millisecond).
   127  				BuildContext(ctx)
   128  			Expect(err).ToNot(HaveOccurred())
   129  
   130  			// Send the request:
   131  			response, err := connection.Delete().Path("/mypath").Send()
   132  			Expect(err).ToNot(HaveOccurred())
   133  			Expect(response).ToNot(BeNil())
   134  		})
   135  	})
   136  
   137  	Describe("Post with body", func() {
   138  		It("Retries for protocol error", func() {
   139  			// Create a connection with a transport wrapper that returns an error for
   140  			// the first request and 200 for the second.
   141  			connection, err := NewConnectionBuilder().
   142  				Logger(logger).
   143  				Tokens(token).
   144  				TransportWrapper(func(_ http.RoundTripper) http.RoundTripper {
   145  					return CombineTransports(
   146  						ErrorTransport(errors.New("PROTOCOL_ERROR")),
   147  						JSONTransport(http.StatusOK, "{}"),
   148  					)
   149  				}).
   150  				RetryInterval(10 * time.Millisecond).
   151  				BuildContext(ctx)
   152  			Expect(err).ToNot(HaveOccurred())
   153  
   154  			// Send the request:
   155  			response, err := connection.Post().Path("/mypath").String("{}").Send()
   156  			Expect(err).ToNot(HaveOccurred())
   157  			Expect(response).ToNot(BeNil())
   158  		})
   159  
   160  		It("Retries for 429", func() {
   161  			// Create a connection with a transport wrapper that returns 429 for the
   162  			// first request and 200 for the second.
   163  			connection, err := NewConnectionBuilder().
   164  				Logger(logger).
   165  				Tokens(token).
   166  				TransportWrapper(func(_ http.RoundTripper) http.RoundTripper {
   167  					return CombineTransports(
   168  						JSONTransport(http.StatusTooManyRequests, "{}"),
   169  						JSONTransport(http.StatusOK, "{}"),
   170  					)
   171  				}).
   172  				RetryInterval(10 * time.Millisecond).
   173  				BuildContext(ctx)
   174  			Expect(err).ToNot(HaveOccurred())
   175  
   176  			// Send the request:
   177  			response, err := connection.Post().
   178  				Path("/mypath").
   179  				String(`{}`).
   180  				Send()
   181  			Expect(err).ToNot(HaveOccurred())
   182  			Expect(response).ToNot(BeNil())
   183  		})
   184  
   185  		It("Retries for 503", func() {
   186  			// Create a connection with a transport wrapper that returns 503 for the
   187  			// first request and 200 for the second.
   188  			connection, err := NewConnectionBuilder().
   189  				Logger(logger).
   190  				Tokens(token).
   191  				TransportWrapper(func(_ http.RoundTripper) http.RoundTripper {
   192  					return CombineTransports(
   193  						JSONTransport(http.StatusServiceUnavailable, "{}"),
   194  						JSONTransport(http.StatusOK, "{}"),
   195  					)
   196  				}).
   197  				RetryInterval(10 * time.Millisecond).
   198  				BuildContext(ctx)
   199  			Expect(err).ToNot(HaveOccurred())
   200  
   201  			// Send the request:
   202  			response, err := connection.Post().
   203  				Path("/mypath").
   204  				String(`{}`).
   205  				Send()
   206  			Expect(err).ToNot(HaveOccurred())
   207  			Expect(response).ToNot(BeNil())
   208  		})
   209  	})
   210  
   211  	It("Writes error to the debug log", func() {
   212  		// Create a logger that allows us to inspect the messages written to the log:
   213  		var buffer bytes.Buffer
   214  		logger, err := logging.NewStdLoggerBuilder().
   215  			Streams(&buffer, &buffer).
   216  			Debug(true).
   217  			Build()
   218  		Expect(err).ToNot(HaveOccurred())
   219  
   220  		// Create a connection with a transport wrapper that returns an error for
   221  		// the first request and 200 for the second.
   222  		connection, err := NewConnectionBuilder().
   223  			Logger(logger).
   224  			Tokens(token).
   225  			TransportWrapper(func(_ http.RoundTripper) http.RoundTripper {
   226  				return CombineTransports(
   227  					ErrorTransport(errors.New("PROTOCOL_ERROR")),
   228  					JSONTransport(http.StatusOK, `{}`),
   229  				)
   230  			}).
   231  			RetryInterval(10 * time.Millisecond).
   232  			BuildContext(ctx)
   233  		Expect(err).ToNot(HaveOccurred())
   234  
   235  		// Send the request:
   236  		response, err := connection.Get().Path("/mypath").Send()
   237  		Expect(err).ToNot(HaveOccurred())
   238  		Expect(response).ToNot(BeNil())
   239  
   240  		// Check that the details of the failed request are in the log:
   241  		messages := buffer.String()
   242  		Expect(messages).To(ContainSubstring("GET"))
   243  		Expect(messages).To(ContainSubstring("mypath"))
   244  		Expect(messages).To(ContainSubstring("failed with protocol error"))
   245  		Expect(messages).To(ContainSubstring("PROTOCOL_ERROR"))
   246  	})
   247  
   248  	It("Writes failed response details to the log", func() {
   249  		// Create a logger that allows us to inspect the messages written to the log:
   250  		var buffer bytes.Buffer
   251  		logger, err := logging.NewStdLoggerBuilder().
   252  			Streams(&buffer, &buffer).
   253  			Debug(true).
   254  			Build()
   255  		Expect(err).ToNot(HaveOccurred())
   256  
   257  		// Create a connection with a transport wrapper that returns 503 for the first
   258  		// request and 200 for the second.
   259  		connection, err := NewConnectionBuilder().
   260  			Logger(logger).
   261  			Tokens(token).
   262  			TransportWrapper(func(_ http.RoundTripper) http.RoundTripper {
   263  				return CombineTransports(
   264  					JSONTransport(http.StatusServiceUnavailable, `{
   265  						"reason": "Something failed",
   266  					}`),
   267  					JSONTransport(http.StatusOK, `{}`),
   268  				)
   269  			}).
   270  			RetryInterval(10 * time.Millisecond).
   271  			BuildContext(ctx)
   272  		Expect(err).ToNot(HaveOccurred())
   273  
   274  		// Send the request:
   275  		response, err := connection.Get().Path("/mypath").Send()
   276  		Expect(err).ToNot(HaveOccurred())
   277  		Expect(response).ToNot(BeNil())
   278  
   279  		// Check that the details of the failed request are in the log:
   280  		messages := buffer.String()
   281  		Expect(messages).To(ContainSubstring("failed with code 503"))
   282  		Expect(messages).To(ContainSubstring(`"reason": "Something failed"`))
   283  	})
   284  })