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 })