Skip to content

Commit 8f93f92

Browse files
authored
release(required): Amplify JS release (#14317)
2 parents 74aa6e5 + 65207a6 commit 8f93f92

File tree

82 files changed

+1521
-536
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+1521
-536
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import { composeTransferHandler } from '@aws-amplify/core/internals/aws-client-utils/composers';
4+
import {
5+
fetchTransferHandler,
6+
retryMiddlewareFactory,
7+
signingMiddlewareFactory,
8+
userAgentMiddlewareFactory,
9+
} from '@aws-amplify/core/internals/aws-client-utils';
10+
11+
import { authenticatedHandler } from '../../../../src/apis/common/baseHandlers/authenticatedHandler';
12+
13+
jest.mock('@aws-amplify/core/internals/aws-client-utils/composers', () => ({
14+
composeTransferHandler: jest
15+
.fn()
16+
.mockReturnValue('composed_transfer_handler'),
17+
}));
18+
19+
describe('authenticated handler', () => {
20+
it('should be configured correctly', () => {
21+
expect(authenticatedHandler).toEqual('composed_transfer_handler');
22+
expect(composeTransferHandler).toHaveBeenCalledTimes(1);
23+
expect(composeTransferHandler).toHaveBeenCalledWith(fetchTransferHandler, [
24+
userAgentMiddlewareFactory,
25+
retryMiddlewareFactory,
26+
signingMiddlewareFactory,
27+
]);
28+
});
29+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import { composeTransferHandler } from '@aws-amplify/core/internals/aws-client-utils/composers';
4+
import {
5+
fetchTransferHandler,
6+
retryMiddlewareFactory,
7+
userAgentMiddlewareFactory,
8+
} from '@aws-amplify/core/internals/aws-client-utils';
9+
10+
import { unauthenticatedHandler } from '../../../../src/apis/common/baseHandlers/unauthenticatedHandler';
11+
12+
jest.mock('@aws-amplify/core/internals/aws-client-utils/composers', () => ({
13+
composeTransferHandler: jest
14+
.fn()
15+
.mockReturnValue('composed_transfer_handler'),
16+
}));
17+
18+
describe('unauthenticated handler', () => {
19+
it('should be configured correctly', () => {
20+
expect(unauthenticatedHandler).toEqual('composed_transfer_handler');
21+
expect(composeTransferHandler).toHaveBeenCalledTimes(1);
22+
expect(composeTransferHandler).toHaveBeenCalledWith(fetchTransferHandler, [
23+
userAgentMiddlewareFactory,
24+
retryMiddlewareFactory,
25+
]);
26+
});
27+
});

packages/api-rest/__tests__/apis/common/internalPost.test.ts

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,27 @@
44
import { AmplifyClassV6 } from '@aws-amplify/core';
55
import { ApiError } from '@aws-amplify/core/internals/utils';
66
import {
7-
authenticatedHandler,
7+
getRetryDecider,
88
parseJsonError,
9-
unauthenticatedHandler,
109
} from '@aws-amplify/core/internals/aws-client-utils';
1110

1211
import {
1312
cancel,
1413
post,
1514
updateRequestToBeCancellable,
1615
} from '../../../src/apis/common/internalPost';
16+
import { authenticatedHandler } from '../../../src/apis/common/baseHandlers/authenticatedHandler';
17+
import { unauthenticatedHandler } from '../../../src/apis/common/baseHandlers/unauthenticatedHandler';
1718
import { RestApiError, isCancelError } from '../../../src/errors';
1819

1920
jest.mock('@aws-amplify/core/internals/aws-client-utils');
21+
jest.mock('../../../src/apis/common/baseHandlers/authenticatedHandler');
22+
jest.mock('../../../src/apis/common/baseHandlers/unauthenticatedHandler');
2023

21-
const mockAuthenticatedHandler = authenticatedHandler as jest.Mock;
22-
const mockUnauthenticatedHandler = unauthenticatedHandler as jest.Mock;
23-
const mockParseJsonError = parseJsonError as jest.Mock;
24+
const mockAuthenticatedHandler = jest.mocked(authenticatedHandler);
25+
const mockUnauthenticatedHandler = jest.mocked(unauthenticatedHandler);
26+
const mockParseJsonError = jest.mocked(parseJsonError);
27+
const mockGetRetryDecider = jest.mocked(getRetryDecider);
2428
const mockFetchAuthSession = jest.fn();
2529
const mockAmplifyInstance = {
2630
Auth: {
@@ -45,13 +49,17 @@ const credentials = {
4549
sessionToken: 'sessionToken',
4650
secretAccessKey: 'secretAccessKey',
4751
};
52+
const retryExpectedResponse = { retryable: true };
53+
const mockGetRetryDeciderResponse = () =>
54+
Promise.resolve(retryExpectedResponse);
4855

4956
describe('internal post', () => {
5057
beforeEach(() => {
5158
jest.resetAllMocks();
5259
mockFetchAuthSession.mockResolvedValue({ credentials });
5360
mockAuthenticatedHandler.mockResolvedValue(successResponse);
5461
mockUnauthenticatedHandler.mockResolvedValue(successResponse);
62+
mockGetRetryDecider.mockReturnValue(mockGetRetryDeciderResponse);
5563
});
5664

5765
it('should call authenticatedHandler with specified region from signingServiceInfo', async () => {
@@ -320,11 +328,12 @@ describe('internal post', () => {
320328
},
321329
};
322330
mockParseJsonError.mockImplementationOnce(async response => {
323-
const errorResponsePayload = await response.body?.json();
331+
const errorResponsePayload = await response?.body?.json();
324332
const error = new Error(errorResponsePayload.message);
325333

326334
return Object.assign(error, {
327335
name: errorResponsePayload.name,
336+
$metadata: {},
328337
});
329338
});
330339
mockUnauthenticatedHandler.mockResolvedValueOnce(errorResponse);
@@ -365,11 +374,12 @@ describe('internal post', () => {
365374
},
366375
};
367376
mockParseJsonError.mockImplementationOnce(async response => {
368-
const errorResponsePayload = await response.body?.json();
377+
const errorResponsePayload = await response?.body?.json();
369378
const error = new Error(errorResponsePayload.message);
370379

371380
return Object.assign(error, {
372381
name: errorResponsePayload.name,
382+
$metadata: {},
373383
});
374384
});
375385
mockUnauthenticatedHandler.mockResolvedValueOnce(errorResponse);
@@ -396,4 +406,52 @@ describe('internal post', () => {
396406
});
397407
}
398408
});
409+
410+
it('should use jittered-exponential-backoff retry strategy', async () => {
411+
expect.assertions(2);
412+
await post(mockAmplifyInstance, {
413+
url: apiGatewayUrl,
414+
options: {
415+
signingServiceInfo: {},
416+
},
417+
});
418+
expect(mockAuthenticatedHandler).toHaveBeenCalledWith(
419+
expect.anything(),
420+
expect.objectContaining({ retryDecider: expect.any(Function) }),
421+
);
422+
const callArgs = mockAuthenticatedHandler.mock.calls[0];
423+
const { retryDecider } = callArgs[1];
424+
const retryDeciderResult = await retryDecider();
425+
expect(retryDeciderResult).toEqual(retryExpectedResponse);
426+
});
427+
428+
it('should use jittered-exponential-backoff retry strategy, even when configuring using library options', async () => {
429+
expect.assertions(2);
430+
const mockAmplifyInstanceWithNoRetry = {
431+
...mockAmplifyInstance,
432+
libraryOptions: {
433+
API: {
434+
REST: {
435+
retryStrategy: {
436+
strategy: 'no-retry',
437+
},
438+
},
439+
},
440+
},
441+
} as any as AmplifyClassV6;
442+
await post(mockAmplifyInstanceWithNoRetry, {
443+
url: apiGatewayUrl,
444+
options: {
445+
signingServiceInfo: {},
446+
},
447+
});
448+
expect(mockAuthenticatedHandler).toHaveBeenCalledWith(
449+
expect.anything(),
450+
expect.objectContaining({ retryDecider: expect.any(Function) }),
451+
);
452+
const callArgs = mockAuthenticatedHandler.mock.calls[0];
453+
const { retryDecider } = callArgs[1];
454+
const retryDeciderResult = await retryDecider();
455+
expect(retryDeciderResult).toEqual(retryExpectedResponse);
456+
});
399457
});

packages/api-rest/__tests__/apis/common/publicApis.test.ts

Lines changed: 164 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33

44
import { AmplifyClassV6 } from '@aws-amplify/core';
55
import {
6-
authenticatedHandler,
6+
getRetryDecider,
77
parseJsonError,
8-
unauthenticatedHandler,
98
} from '@aws-amplify/core/internals/aws-client-utils';
109
import { ApiError } from '@aws-amplify/core/internals/utils';
1110

11+
import { authenticatedHandler } from '../../../src/apis/common/baseHandlers/authenticatedHandler';
12+
import { unauthenticatedHandler } from '../../../src/apis/common/baseHandlers/unauthenticatedHandler';
1213
import {
1314
del,
1415
get,
@@ -26,6 +27,8 @@ import {
2627
import { RestApiResponse } from '../../../src/types';
2728

2829
jest.mock('@aws-amplify/core/internals/aws-client-utils');
30+
jest.mock('../../../src/apis/common/baseHandlers/authenticatedHandler');
31+
jest.mock('../../../src/apis/common/baseHandlers/unauthenticatedHandler');
2932

3033
const mockAuthenticatedHandler = authenticatedHandler as jest.Mock;
3134
const mockUnauthenticatedHandler = unauthenticatedHandler as jest.Mock;
@@ -75,6 +78,10 @@ const mockSuccessResponse = {
7578
text: jest.fn(),
7679
},
7780
};
81+
const mockGetRetryDecider = getRetryDecider as jest.Mock;
82+
const mockRetryResponse = { retryable: true };
83+
const mockNoRetryResponse = { retryable: false };
84+
const mockRetryDeciderResponse = () => Promise.resolve(mockRetryResponse);
7885

7986
describe('public APIs', () => {
8087
beforeEach(() => {
@@ -86,6 +93,7 @@ describe('public APIs', () => {
8693
mockAuthenticatedHandler.mockResolvedValue(mockSuccessResponse);
8794
mockUnauthenticatedHandler.mockResolvedValue(mockSuccessResponse);
8895
mockGetConfig.mockReturnValue(mockConfig);
96+
mockGetRetryDecider.mockReturnValue(mockRetryDeciderResponse);
8997
});
9098
const APIs = [
9199
{ name: 'get', fn: get, method: 'GET' },
@@ -415,6 +423,160 @@ describe('public APIs', () => {
415423
expect(error.message).toBe(cancelMessage);
416424
}
417425
});
426+
427+
describe('retry strategy', () => {
428+
beforeEach(() => {
429+
mockAuthenticatedHandler.mockReset();
430+
mockAuthenticatedHandler.mockResolvedValue(mockSuccessResponse);
431+
});
432+
433+
it('should not retry when retry is set to "no-retry"', async () => {
434+
expect.assertions(2);
435+
await fn(mockAmplifyInstance, {
436+
apiName: 'restApi1',
437+
path: '/items',
438+
retryStrategy: {
439+
strategy: 'no-retry',
440+
},
441+
}).response;
442+
expect(mockAuthenticatedHandler).toHaveBeenCalledWith(
443+
expect.any(Object),
444+
expect.objectContaining({ retryDecider: expect.any(Function) }),
445+
);
446+
const callArgs = mockAuthenticatedHandler.mock.calls[0];
447+
const { retryDecider } = callArgs[1];
448+
const result = await retryDecider();
449+
expect(result).toEqual(mockNoRetryResponse);
450+
});
451+
452+
it('should retry when retry is set to "jittered-exponential-backoff"', async () => {
453+
expect.assertions(2);
454+
await fn(mockAmplifyInstance, {
455+
apiName: 'restApi1',
456+
path: '/items',
457+
retryStrategy: {
458+
strategy: 'jittered-exponential-backoff',
459+
},
460+
}).response;
461+
expect(mockAuthenticatedHandler).toHaveBeenCalledWith(
462+
expect.any(Object),
463+
expect.objectContaining({ retryDecider: expect.any(Function) }),
464+
);
465+
const callArgs = mockAuthenticatedHandler.mock.calls[0];
466+
const { retryDecider } = callArgs[1];
467+
const result = await retryDecider();
468+
expect(result).toEqual(mockRetryResponse);
469+
});
470+
471+
it('should retry when retry strategy is not provided', async () => {
472+
expect.assertions(2);
473+
await fn(mockAmplifyInstance, {
474+
apiName: 'restApi1',
475+
path: '/items',
476+
}).response;
477+
expect(mockAuthenticatedHandler).toHaveBeenCalledWith(
478+
expect.any(Object),
479+
expect.objectContaining({ retryDecider: expect.any(Function) }),
480+
);
481+
const callArgs = mockAuthenticatedHandler.mock.calls[0];
482+
const { retryDecider } = callArgs[1];
483+
const result = await retryDecider();
484+
expect(result).toEqual(mockRetryResponse);
485+
});
486+
487+
it('should retry and prefer the individual retry strategy over the library options', async () => {
488+
expect.assertions(2);
489+
const mockAmplifyInstanceWithNoRetry = {
490+
...mockAmplifyInstance,
491+
libraryOptions: {
492+
API: {
493+
REST: {
494+
retryStrategy: {
495+
strategy: 'no-retry',
496+
},
497+
},
498+
},
499+
},
500+
} as any as AmplifyClassV6;
501+
await fn(mockAmplifyInstanceWithNoRetry, {
502+
apiName: 'restApi1',
503+
path: 'items',
504+
retryStrategy: {
505+
strategy: 'jittered-exponential-backoff',
506+
},
507+
}).response;
508+
509+
expect(mockAuthenticatedHandler).toHaveBeenCalledWith(
510+
expect.any(Object),
511+
expect.objectContaining({ retryDecider: expect.any(Function) }),
512+
);
513+
const callArgs = mockAuthenticatedHandler.mock.calls[0];
514+
const { retryDecider } = callArgs[1];
515+
const result = await retryDecider();
516+
expect(result).toEqual(mockRetryResponse);
517+
});
518+
519+
it('should not retry and prefer the individual retry strategy over the library options', async () => {
520+
expect.assertions(2);
521+
const mockAmplifyInstanceWithRetry = {
522+
...mockAmplifyInstance,
523+
libraryOptions: {
524+
API: {
525+
REST: {
526+
retryStrategy: {
527+
strategy: 'jittered-exponential-backoff',
528+
},
529+
},
530+
},
531+
},
532+
} as any as AmplifyClassV6;
533+
await fn(mockAmplifyInstanceWithRetry, {
534+
apiName: 'restApi1',
535+
path: 'items',
536+
retryStrategy: {
537+
strategy: 'no-retry',
538+
},
539+
}).response;
540+
541+
expect(mockAuthenticatedHandler).toHaveBeenCalledWith(
542+
expect.any(Object),
543+
expect.objectContaining({ retryDecider: expect.any(Function) }),
544+
);
545+
const callArgs = mockAuthenticatedHandler.mock.calls[0];
546+
const { retryDecider } = callArgs[1];
547+
const result = await retryDecider();
548+
expect(result).toEqual(mockNoRetryResponse);
549+
});
550+
551+
it('should not retry when configured through library options', async () => {
552+
expect.assertions(2);
553+
const mockAmplifyInstanceWithRetry = {
554+
...mockAmplifyInstance,
555+
libraryOptions: {
556+
API: {
557+
REST: {
558+
retryStrategy: {
559+
strategy: 'no-retry',
560+
},
561+
},
562+
},
563+
},
564+
} as any as AmplifyClassV6;
565+
await fn(mockAmplifyInstanceWithRetry, {
566+
apiName: 'restApi1',
567+
path: 'items',
568+
}).response;
569+
570+
expect(mockAuthenticatedHandler).toHaveBeenCalledWith(
571+
expect.any(Object),
572+
expect.objectContaining({ retryDecider: expect.any(Function) }),
573+
);
574+
const callArgs = mockAuthenticatedHandler.mock.calls[0];
575+
const { retryDecider } = callArgs[1];
576+
const result = await retryDecider();
577+
expect(result).toEqual(mockNoRetryResponse);
578+
});
579+
});
418580
});
419581
});
420582
});

0 commit comments

Comments
 (0)