Skip to content

Commit 0230e19

Browse files
committed
fix(api-rest): refactor ajax method to not relying on side effects
1 parent 91d263f commit 0230e19

File tree

2 files changed

+74
-88
lines changed

2 files changed

+74
-88
lines changed

packages/api-rest/__tests__/RestAPI-test.ts

Lines changed: 37 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { Signer, Credentials, DateUtils } from '@aws-amplify/core';
55

66
jest.mock('axios');
77

8+
const mockAxios = jest.spyOn(axios as any, 'default');
9+
810
axios.CancelToken = <CancelTokenStatic>{
911
source: () => ({ token: null, cancel: null }),
1012
};
@@ -28,6 +30,7 @@ const config = {
2830

2931
afterEach(() => {
3032
jest.restoreAllMocks();
33+
mockAxios.mockClear();
3134
});
3235

3336
describe('Rest API test', () => {
@@ -166,19 +169,11 @@ describe('Rest API test', () => {
166169
api.configure(custom_config);
167170
const spyon = jest
168171
.spyOn(Credentials, 'get')
169-
.mockImplementationOnce(() => {
170-
return new Promise((res, rej) => {
171-
res('cred');
172-
});
173-
});
172+
.mockResolvedValueOnce('cred');
174173

175174
const spyonRequest = jest
176175
.spyOn(RestClient.prototype as any, '_request')
177-
.mockImplementationOnce(() => {
178-
return new Promise((res, rej) => {
179-
res({});
180-
});
181-
});
176+
.mockResolvedValueOnce({});
182177
await api.get('apiName', 'path', {});
183178

184179
expect(spyonRequest).toBeCalledWith(
@@ -221,25 +216,15 @@ describe('Rest API test', () => {
221216
session_token: 'token',
222217
};
223218

224-
const spyon = jest.spyOn(Credentials, 'get').mockImplementation(() => {
225-
return new Promise((res, rej) => {
226-
res(creds);
227-
});
228-
});
219+
const spyon = jest.spyOn(Credentials, 'get').mockResolvedValue(creds);
229220

230221
const spyonSigner = jest
231222
.spyOn(Signer, 'sign')
232223
.mockImplementationOnce(() => {
233224
return { headers: {} };
234225
});
235226

236-
const spyAxios = jest
237-
.spyOn(axios as any, 'default')
238-
.mockImplementationOnce(() => {
239-
return new Promise((res, rej) => {
240-
res(resp);
241-
});
242-
});
227+
mockAxios.mockResolvedValue(resp);
243228

244229
const init = {
245230
timeout: 2500,
@@ -297,13 +282,7 @@ describe('Rest API test', () => {
297282
return { headers: {} };
298283
});
299284

300-
const spyAxios = jest
301-
.spyOn(axios as any, 'default')
302-
.mockImplementationOnce(() => {
303-
return new Promise((res, rej) => {
304-
res(resp);
305-
});
306-
});
285+
mockAxios.mockResolvedValue(resp);
307286

308287
const init = {
309288
queryStringParameters: {
@@ -363,13 +342,7 @@ describe('Rest API test', () => {
363342
return { headers: {} };
364343
});
365344

366-
const spyAxios = jest
367-
.spyOn(axios as any, 'default')
368-
.mockImplementationOnce(() => {
369-
return new Promise((res, rej) => {
370-
res(resp);
371-
});
372-
});
345+
mockAxios.mockResolvedValue(resp);
373346

374347
const init = {
375348
queryStringParameters: {
@@ -429,13 +402,7 @@ describe('Rest API test', () => {
429402
return { headers: {} };
430403
});
431404

432-
const spyAxios = jest
433-
.spyOn(axios as any, 'default')
434-
.mockImplementationOnce(() => {
435-
return new Promise((res, rej) => {
436-
res(resp);
437-
});
438-
});
405+
mockAxios.mockResolvedValue(resp);
439406

440407
const init = {
441408
queryStringParameters: {
@@ -558,18 +525,39 @@ describe('Rest API test', () => {
558525

559526
jest.spyOn(Credentials, 'get').mockResolvedValue('creds');
560527

561-
jest
562-
.spyOn(RestClient.prototype as any, '_signed')
563-
.mockRejectedValueOnce(normalError);
528+
// jest
529+
// .spyOn(RestClient.prototype as any, '_signed')
530+
// .mockRejectedValueOnce(normalError);
531+
532+
jest.spyOn(RestClient.prototype as any, '_sign').mockReturnValue({
533+
...init,
534+
headers: { ...init.headers, Authorization: 'signed' },
535+
});
536+
mockAxios.mockImplementationOnce(() => {
537+
return new Promise((_, rej) => {
538+
rej(normalError);
539+
});
540+
});
564541

565542
await expect(api.post('url', 'path', init)).rejects.toThrow(normalError);
566543

567544
// Clock should not be skewed from normal errors
568545
expect(DateUtils.getClockOffset()).toBe(0);
569546

570-
jest
571-
.spyOn(RestClient.prototype as any, '_signed')
572-
.mockRejectedValueOnce(clockSkewError);
547+
// jest
548+
// .spyOn(RestClient.prototype as any, '_signed')
549+
// .mockRejectedValueOnce(clockSkewError);
550+
551+
// mock clock skew error response and successful response after retry
552+
mockAxios
553+
.mockImplementationOnce(() => {
554+
return new Promise((_, rej) => {
555+
rej(clockSkewError);
556+
});
557+
})
558+
.mockResolvedValue({
559+
data: [{ name: 'Bob' }],
560+
});
573561

574562
await expect(api.post('url', 'path', init)).resolves.toEqual([
575563
{ name: 'Bob' },

packages/api-rest/src/RestClient.ts

Lines changed: 37 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -180,39 +180,42 @@ export class RestClient {
180180
return this._request(params, isAllResponse);
181181
}
182182

183-
// Signing the request in case there credentials are available
184-
return this.Credentials.get().then(
185-
credentials => {
186-
return this._signed({ ...params }, credentials, isAllResponse, {
187-
region,
188-
service,
189-
}).catch(error => {
190-
if (DateUtils.isClockSkewError(error)) {
191-
const { headers } = error.response;
192-
const dateHeader = headers && (headers.date || headers.Date);
193-
const responseDate = new Date(dateHeader);
194-
const requestDate = DateUtils.getDateFromHeaderString(
195-
params.headers['x-amz-date']
196-
);
197-
198-
// Compare local clock to the server clock
199-
if (DateUtils.isClockSkewed(responseDate)) {
200-
DateUtils.setClockOffset(
201-
responseDate.getTime() - requestDate.getTime()
202-
);
203-
204-
return this.ajax(urlOrApiInfo, method, init);
205-
}
206-
}
207-
208-
throw error;
209-
});
210-
},
211-
err => {
212-
logger.debug('No credentials available, the request will be unsigned');
213-
return this._request(params, isAllResponse);
183+
let credentials;
184+
try {
185+
credentials = await this.Credentials.get();
186+
} catch (error) {
187+
logger.debug('No credentials available, the request will be unsigned');
188+
return this._request(params, isAllResponse);
189+
}
190+
let signedParams;
191+
try {
192+
signedParams = this._sign({ ...params }, credentials, {
193+
region,
194+
service,
195+
});
196+
const response = await axios(signedParams);
197+
return isAllResponse ? response : response.data;
198+
} catch (error) {
199+
logger.debug(error);
200+
if (DateUtils.isClockSkewError(error)) {
201+
const { headers } = error.response;
202+
const dateHeader = headers && (headers.date || headers.Date);
203+
const responseDate = new Date(dateHeader);
204+
const requestDate = DateUtils.getDateFromHeaderString(
205+
signedParams.headers['x-amz-date']
206+
);
207+
208+
// Compare local clock to the server clock
209+
if (DateUtils.isClockSkewed(responseDate)) {
210+
DateUtils.setClockOffset(
211+
responseDate.getTime() - requestDate.getTime()
212+
);
213+
214+
return this.ajax(urlOrApiInfo, method, init);
215+
}
214216
}
215-
);
217+
throw error;
218+
}
216219
}
217220

218221
/**
@@ -365,7 +368,7 @@ export class RestClient {
365368

366369
/** private methods **/
367370

368-
private _signed(params, credentials, isAllResponse, { service, region }) {
371+
private _sign(params, credentials, { service, region }) {
369372
const { signerServiceInfo: signerServiceInfoParams, ...otherParams } =
370373
params;
371374

@@ -400,12 +403,7 @@ export class RestClient {
400403

401404
delete signed_params.headers['host'];
402405

403-
return axios(signed_params)
404-
.then(response => (isAllResponse ? response : response.data))
405-
.catch(error => {
406-
logger.debug(error);
407-
throw error;
408-
});
406+
return signed_params;
409407
}
410408

411409
private _request(params, isAllResponse = false) {

0 commit comments

Comments
 (0)