1
1
import { HttpRequest , HttpResponse } from "@aws-sdk/protocol-http" ;
2
2
import { isServerError , isThrottlingError , isTransientError } from "@aws-sdk/service-error-classification" ;
3
- import { FinalizeHandlerArguments , HandlerExecutionContext , MiddlewareStack , RetryErrorType } from "@aws-sdk/types" ;
3
+ import { FinalizeHandlerArguments , HandlerExecutionContext , MiddlewareStack } from "@aws-sdk/types" ;
4
4
import { INVOCATION_ID_HEADER , REQUEST_HEADER } from "@aws-sdk/util-retry" ;
5
5
import { v4 } from "uuid" ;
6
6
@@ -99,6 +99,13 @@ describe(retryMiddleware.name, () => {
99
99
refreshRetryTokenForRetry : jest . fn ( ) . mockResolvedValue ( mockRetryToken ) ,
100
100
recordSuccess : jest . fn ( ) ,
101
101
} ;
102
+ const mockResponse = "mockResponse" ;
103
+ const mockSuccess = {
104
+ response : mockResponse ,
105
+ output : {
106
+ $metadata : { } ,
107
+ } ,
108
+ } ;
102
109
const getErrorWithValues = ( retryAfter : number | string , retryAfterHeaderName ?: string ) => {
103
110
const error = new Error ( "mockError" ) ;
104
111
Object . defineProperty ( error , "$response" , {
@@ -110,8 +117,8 @@ describe(retryMiddleware.name, () => {
110
117
} ;
111
118
112
119
it ( "calls acquireInitialRetryToken and records success when next succeeds" , async ( ) => {
113
- const next = jest . fn ( ) ;
114
- await retryMiddleware ( {
120
+ const next = jest . fn ( ) . mockResolvedValueOnce ( mockSuccess ) ;
121
+ const { response , output } = await retryMiddleware ( {
115
122
maxAttempts : ( ) => Promise . resolve ( maxAttempts ) ,
116
123
retryStrategy : jest . fn ( ) . mockResolvedValue ( { ...mockRetryStrategy , maxAttempts } ) ,
117
124
} ) (
@@ -122,6 +129,7 @@ describe(retryMiddleware.name, () => {
122
129
expect ( mockRetryStrategy . acquireInitialRetryToken ) . toHaveBeenCalledWith ( partitionId ) ;
123
130
expect ( mockRetryStrategy . recordSuccess ) . toHaveBeenCalledTimes ( 1 ) ;
124
131
expect ( mockRetryStrategy . recordSuccess ) . toHaveBeenCalledWith ( mockRetryToken ) ;
132
+ expect ( output . $metadata . attempts ) . toBe ( 1 ) ;
125
133
} ) ;
126
134
127
135
describe ( "throws when token cannot be refreshed" , ( ) => {
@@ -138,8 +146,6 @@ describe(retryMiddleware.name, () => {
138
146
refreshRetryTokenForRetry : jest . fn ( ) . mockRejectedValue ( new Error ( "Cannot refresh token" ) ) ,
139
147
recordSuccess : jest . fn ( ) ,
140
148
} ;
141
- // const maxAttempts = 3;
142
- // const retryCount = maxAttempts - 1;
143
149
try {
144
150
await retryMiddleware ( {
145
151
maxAttempts : ( ) => Promise . resolve ( maxAttempts ) ,
@@ -154,6 +160,8 @@ describe(retryMiddleware.name, () => {
154
160
expect ( mockRetryStrategy . refreshRetryTokenForRetry ) . toHaveBeenCalledTimes ( 1 ) ;
155
161
expect ( mockRetryStrategy . refreshRetryTokenForRetry ) . toHaveBeenCalledWith ( mockRetryToken , errorInfo ) ;
156
162
expect ( error ) . toStrictEqual ( requestError ) ;
163
+ expect ( error . $metadata . attempts ) . toBe ( 1 ) ;
164
+ expect ( error . $metadata . totalRetryDelay ) . toBeDefined ( ) ;
157
165
}
158
166
} ) ;
159
167
} ) ;
@@ -162,11 +170,11 @@ describe(retryMiddleware.name, () => {
162
170
const mockError = new Error ( "mockError" ) ;
163
171
it ( "sets throttling error type" , async ( ) => {
164
172
( isThrottlingError as jest . Mock ) . mockReturnValue ( true ) ;
165
- const next = jest . fn ( ) . mockRejectedValueOnce ( mockError ) . mockResolvedValueOnce ( { } ) ;
173
+ const next = jest . fn ( ) . mockRejectedValueOnce ( mockError ) . mockResolvedValueOnce ( mockSuccess ) ;
166
174
const errorInfo = {
167
175
errorType : "THROTTLING" ,
168
176
} ;
169
- await retryMiddleware ( {
177
+ const { response , output } = await retryMiddleware ( {
170
178
maxAttempts : ( ) => Promise . resolve ( maxAttempts ) ,
171
179
retryStrategy : jest . fn ( ) . mockResolvedValue ( { ...mockRetryStrategy , maxAttempts } ) ,
172
180
} ) (
@@ -177,15 +185,17 @@ describe(retryMiddleware.name, () => {
177
185
expect ( mockRetryStrategy . acquireInitialRetryToken ) . toHaveBeenCalledWith ( partitionId ) ;
178
186
expect ( mockRetryStrategy . refreshRetryTokenForRetry ) . toHaveBeenCalledTimes ( 1 ) ;
179
187
expect ( mockRetryStrategy . refreshRetryTokenForRetry ) . toHaveBeenCalledWith ( mockRetryToken , errorInfo ) ;
188
+ expect ( output . $metadata . attempts ) . toBe ( 2 ) ;
189
+ expect ( output . $metadata . totalRetryDelay ) . toBeDefined ( ) ;
180
190
} ) ;
181
191
it ( "sets transient error type" , async ( ) => {
182
192
( isTransientError as jest . Mock ) . mockReturnValue ( true ) ;
183
193
( isThrottlingError as jest . Mock ) . mockReturnValue ( false ) ;
184
- const next = jest . fn ( ) . mockRejectedValueOnce ( mockError ) . mockResolvedValueOnce ( { } ) ;
194
+ const next = jest . fn ( ) . mockRejectedValueOnce ( mockError ) . mockResolvedValueOnce ( mockSuccess ) ;
185
195
const errorInfo = {
186
196
errorType : "TRANSIENT" ,
187
197
} ;
188
- await retryMiddleware ( {
198
+ const { response , output } = await retryMiddleware ( {
189
199
maxAttempts : ( ) => Promise . resolve ( maxAttempts ) ,
190
200
retryStrategy : jest . fn ( ) . mockResolvedValue ( { ...mockRetryStrategy , maxAttempts } ) ,
191
201
} ) (
@@ -196,16 +206,18 @@ describe(retryMiddleware.name, () => {
196
206
expect ( mockRetryStrategy . acquireInitialRetryToken ) . toHaveBeenCalledWith ( partitionId ) ;
197
207
expect ( mockRetryStrategy . refreshRetryTokenForRetry ) . toHaveBeenCalledTimes ( 1 ) ;
198
208
expect ( mockRetryStrategy . refreshRetryTokenForRetry ) . toHaveBeenCalledWith ( mockRetryToken , errorInfo ) ;
209
+ expect ( output . $metadata . attempts ) . toBe ( 2 ) ;
210
+ expect ( output . $metadata . totalRetryDelay ) . toBeDefined ( ) ;
199
211
} ) ;
200
212
it ( "sets server error type" , async ( ) => {
201
213
( isServerError as jest . Mock ) . mockReturnValue ( true ) ;
202
214
( isTransientError as jest . Mock ) . mockReturnValue ( false ) ;
203
215
( isThrottlingError as jest . Mock ) . mockReturnValue ( false ) ;
204
- const next = jest . fn ( ) . mockRejectedValueOnce ( mockError ) . mockResolvedValueOnce ( { } ) ;
216
+ const next = jest . fn ( ) . mockRejectedValueOnce ( mockError ) . mockResolvedValueOnce ( mockSuccess ) ;
205
217
const errorInfo = {
206
218
errorType : "SERVER_ERROR" ,
207
219
} ;
208
- await retryMiddleware ( {
220
+ const { response , output } = await retryMiddleware ( {
209
221
maxAttempts : ( ) => Promise . resolve ( maxAttempts ) ,
210
222
retryStrategy : jest . fn ( ) . mockResolvedValue ( { ...mockRetryStrategy , maxAttempts } ) ,
211
223
} ) (
@@ -216,16 +228,18 @@ describe(retryMiddleware.name, () => {
216
228
expect ( mockRetryStrategy . acquireInitialRetryToken ) . toHaveBeenCalledWith ( partitionId ) ;
217
229
expect ( mockRetryStrategy . refreshRetryTokenForRetry ) . toHaveBeenCalledTimes ( 1 ) ;
218
230
expect ( mockRetryStrategy . refreshRetryTokenForRetry ) . toHaveBeenCalledWith ( mockRetryToken , errorInfo ) ;
231
+ expect ( output . $metadata . attempts ) . toBe ( 2 ) ;
232
+ expect ( output . $metadata . totalRetryDelay ) . toBeDefined ( ) ;
219
233
} ) ;
220
234
it ( "sets client error type" , async ( ) => {
221
235
( isServerError as jest . Mock ) . mockReturnValue ( false ) ;
222
236
( isTransientError as jest . Mock ) . mockReturnValue ( false ) ;
223
237
( isThrottlingError as jest . Mock ) . mockReturnValue ( false ) ;
224
- const next = jest . fn ( ) . mockRejectedValueOnce ( mockError ) . mockResolvedValueOnce ( { } ) ;
238
+ const next = jest . fn ( ) . mockRejectedValueOnce ( mockError ) . mockResolvedValueOnce ( mockSuccess ) ;
225
239
const errorInfo = {
226
240
errorType : "CLIENT_ERROR" ,
227
241
} ;
228
- await retryMiddleware ( {
242
+ const { response , output } = await retryMiddleware ( {
229
243
maxAttempts : ( ) => Promise . resolve ( maxAttempts ) ,
230
244
retryStrategy : jest . fn ( ) . mockResolvedValue ( { ...mockRetryStrategy , maxAttempts } ) ,
231
245
} ) (
@@ -236,6 +250,8 @@ describe(retryMiddleware.name, () => {
236
250
expect ( mockRetryStrategy . acquireInitialRetryToken ) . toHaveBeenCalledWith ( partitionId ) ;
237
251
expect ( mockRetryStrategy . refreshRetryTokenForRetry ) . toHaveBeenCalledTimes ( 1 ) ;
238
252
expect ( mockRetryStrategy . refreshRetryTokenForRetry ) . toHaveBeenCalledWith ( mockRetryToken , errorInfo ) ;
253
+ expect ( output . $metadata . attempts ) . toBe ( 2 ) ;
254
+ expect ( output . $metadata . totalRetryDelay ) . toBeDefined ( ) ;
239
255
} ) ;
240
256
241
257
describe ( "when retry-after is not set" , ( ) => {
@@ -247,11 +263,11 @@ describe(retryMiddleware.name, () => {
247
263
headers : { [ "other-header" ] : "foo" } ,
248
264
} ,
249
265
} ) ;
250
- const next = jest . fn ( ) . mockRejectedValueOnce ( mockError ) . mockResolvedValueOnce ( { } ) ;
266
+ const next = jest . fn ( ) . mockRejectedValueOnce ( mockError ) . mockResolvedValueOnce ( mockSuccess ) ;
251
267
const errorInfo = {
252
268
errorType : "CLIENT_ERROR" ,
253
269
} ;
254
- await retryMiddleware ( {
270
+ const { response , output } = await retryMiddleware ( {
255
271
maxAttempts : ( ) => Promise . resolve ( maxAttempts ) ,
256
272
retryStrategy : jest . fn ( ) . mockResolvedValue ( { ...mockRetryStrategy , maxAttempts } ) ,
257
273
} ) (
@@ -262,6 +278,8 @@ describe(retryMiddleware.name, () => {
262
278
expect ( mockRetryStrategy . acquireInitialRetryToken ) . toHaveBeenCalledWith ( partitionId ) ;
263
279
expect ( mockRetryStrategy . refreshRetryTokenForRetry ) . toHaveBeenCalledTimes ( 1 ) ;
264
280
expect ( mockRetryStrategy . refreshRetryTokenForRetry ) . toHaveBeenCalledWith ( mockRetryToken , errorInfo ) ;
281
+ expect ( output . $metadata . attempts ) . toBe ( 2 ) ;
282
+ expect ( output . $metadata . totalRetryDelay ) . toBeDefined ( ) ;
265
283
} ) ;
266
284
} ) ;
267
285
@@ -276,8 +294,8 @@ describe(retryMiddleware.name, () => {
276
294
} ;
277
295
it ( "parses retry-after from date string" , async ( ) => {
278
296
const error = getErrorWithValues ( retryAfterDate . toISOString ( ) ) ;
279
- const next = jest . fn ( ) . mockRejectedValueOnce ( error ) . mockResolvedValueOnce ( { } ) ;
280
- await retryMiddleware ( {
297
+ const next = jest . fn ( ) . mockRejectedValueOnce ( error ) . mockResolvedValueOnce ( mockSuccess ) ;
298
+ const { response , output } = await retryMiddleware ( {
281
299
maxAttempts : ( ) => Promise . resolve ( maxAttempts ) ,
282
300
retryStrategy : jest . fn ( ) . mockResolvedValue ( { ...mockRetryStrategy , maxAttempts } ) ,
283
301
} ) (
@@ -288,11 +306,13 @@ describe(retryMiddleware.name, () => {
288
306
expect ( mockRetryStrategy . acquireInitialRetryToken ) . toHaveBeenCalledWith ( partitionId ) ;
289
307
expect ( mockRetryStrategy . refreshRetryTokenForRetry ) . toHaveBeenCalledTimes ( 1 ) ;
290
308
expect ( mockRetryStrategy . refreshRetryTokenForRetry ) . toHaveBeenCalledWith ( mockRetryToken , errorInfo ) ;
309
+ expect ( output . $metadata . attempts ) . toBe ( 2 ) ;
310
+ expect ( output . $metadata . totalRetryDelay ) . toBeDefined ( ) ;
291
311
} ) ;
292
312
it ( "parses retry-after from seconds" , async ( ) => {
293
313
const error = getErrorWithValues ( retryAfterDate . getTime ( ) / 1000 ) ;
294
- const next = jest . fn ( ) . mockRejectedValueOnce ( error ) . mockResolvedValueOnce ( { } ) ;
295
- await retryMiddleware ( {
314
+ const next = jest . fn ( ) . mockRejectedValueOnce ( error ) . mockResolvedValueOnce ( mockSuccess ) ;
315
+ const { response , output } = await retryMiddleware ( {
296
316
maxAttempts : ( ) => Promise . resolve ( maxAttempts ) ,
297
317
retryStrategy : jest . fn ( ) . mockResolvedValue ( { ...mockRetryStrategy , maxAttempts } ) ,
298
318
} ) (
@@ -303,11 +323,13 @@ describe(retryMiddleware.name, () => {
303
323
expect ( mockRetryStrategy . acquireInitialRetryToken ) . toHaveBeenCalledWith ( partitionId ) ;
304
324
expect ( mockRetryStrategy . refreshRetryTokenForRetry ) . toHaveBeenCalledTimes ( 1 ) ;
305
325
expect ( mockRetryStrategy . refreshRetryTokenForRetry ) . toHaveBeenCalledWith ( mockRetryToken , errorInfo ) ;
326
+ expect ( output . $metadata . attempts ) . toBe ( 2 ) ;
327
+ expect ( output . $metadata . totalRetryDelay ) . toBeDefined ( ) ;
306
328
} ) ;
307
329
it ( "parses retry-after from Retry-After header name" , async ( ) => {
308
330
const error = getErrorWithValues ( retryAfterDate . toISOString ( ) , "Retry-After" ) ;
309
- const next = jest . fn ( ) . mockRejectedValueOnce ( error ) . mockResolvedValueOnce ( { } ) ;
310
- await retryMiddleware ( {
331
+ const next = jest . fn ( ) . mockRejectedValueOnce ( error ) . mockResolvedValueOnce ( mockSuccess ) ;
332
+ const { response , output } = await retryMiddleware ( {
311
333
maxAttempts : ( ) => Promise . resolve ( maxAttempts ) ,
312
334
retryStrategy : jest . fn ( ) . mockResolvedValue ( { ...mockRetryStrategy , maxAttempts } ) ,
313
335
} ) (
@@ -318,7 +340,8 @@ describe(retryMiddleware.name, () => {
318
340
expect ( mockRetryStrategy . acquireInitialRetryToken ) . toHaveBeenCalledWith ( partitionId ) ;
319
341
expect ( mockRetryStrategy . refreshRetryTokenForRetry ) . toHaveBeenCalledTimes ( 1 ) ;
320
342
expect ( mockRetryStrategy . refreshRetryTokenForRetry ) . toHaveBeenCalledWith ( mockRetryToken , errorInfo ) ;
321
- // (isInstance as unknown as jest.Mock).mockReturnValue(false);
343
+ expect ( output . $metadata . attempts ) . toBe ( 2 ) ;
344
+ expect ( output . $metadata . totalRetryDelay ) . toBeDefined ( ) ;
322
345
} ) ;
323
346
( isInstance as unknown as jest . Mock ) . mockReturnValue ( false ) ;
324
347
} ) ;
@@ -327,7 +350,7 @@ describe(retryMiddleware.name, () => {
327
350
describe ( "retry headers" , ( ) => {
328
351
describe ( "not added if HttpRequest.isInstance returns false" , ( ) => {
329
352
it ( `retry informational header: ${ INVOCATION_ID_HEADER } ` , async ( ) => {
330
- const next = jest . fn ( ) ;
353
+ const next = jest . fn ( ) . mockResolvedValueOnce ( mockSuccess ) ;
331
354
await retryMiddleware ( {
332
355
maxAttempts : ( ) => Promise . resolve ( maxAttempts ) ,
333
356
retryStrategy : jest . fn ( ) . mockResolvedValue ( { ...mockRetryStrategy , maxAttempts } ) ,
@@ -340,7 +363,7 @@ describe(retryMiddleware.name, () => {
340
363
} ) ;
341
364
} ) ;
342
365
it ( `header for each attempt as ${ REQUEST_HEADER } ` , async ( ) => {
343
- const next = jest . fn ( ) ;
366
+ const next = jest . fn ( ) . mockResolvedValueOnce ( mockSuccess ) ;
344
367
await retryMiddleware ( {
345
368
maxAttempts : ( ) => Promise . resolve ( maxAttempts ) ,
346
369
retryStrategy : jest . fn ( ) . mockResolvedValue ( { ...mockRetryStrategy , maxAttempts } ) ,
@@ -359,7 +382,7 @@ describe(retryMiddleware.name, () => {
359
382
const { isInstance } = HttpRequest ;
360
383
( isInstance as unknown as jest . Mock ) . mockReturnValue ( true ) ;
361
384
( isThrottlingError as jest . Mock ) . mockReturnValue ( true ) ;
362
- const next = jest . fn ( ) . mockRejectedValueOnce ( error ) . mockResolvedValueOnce ( { } ) ;
385
+ const next = jest . fn ( ) . mockRejectedValueOnce ( error ) . mockResolvedValueOnce ( mockSuccess ) ;
363
386
await retryMiddleware ( {
364
387
maxAttempts : ( ) => Promise . resolve ( maxAttempts ) ,
365
388
retryStrategy : jest . fn ( ) . mockResolvedValue ( { ...mockRetryStrategy , maxAttempts } ) ,
@@ -377,7 +400,7 @@ describe(retryMiddleware.name, () => {
377
400
const { isInstance } = HttpRequest ;
378
401
( isInstance as unknown as jest . Mock ) . mockReturnValue ( true ) ;
379
402
( isThrottlingError as jest . Mock ) . mockReturnValue ( true ) ;
380
- const next = jest . fn ( ) . mockRejectedValueOnce ( error ) . mockResolvedValueOnce ( { } ) ;
403
+ const next = jest . fn ( ) . mockRejectedValueOnce ( error ) . mockResolvedValueOnce ( mockSuccess ) ;
381
404
await retryMiddleware ( {
382
405
maxAttempts : ( ) => Promise . resolve ( maxAttempts ) ,
383
406
retryStrategy : jest . fn ( ) . mockResolvedValue ( { ...mockRetryStrategy , maxAttempts } ) ,
0 commit comments