Skip to content

Commit 33fe95f

Browse files
lqkallasszechyjs
authored and
GitHub Enterprise
committed
FRIDGE-461 Add error handling to axios requests (#393)
* Fix typings * FRIDGE-450 Fix yarn * FRIDGE-450 Add event * FRIDGE-450 Remove duplicate interface WorkerEvents * FRIDGE-450 Address comments to fix types * FRIDGE-450 Additional fixes * FRIDGE-450 Upgrade typed-emitter to version 2.1.0 * FRIDGE-461 Add axios error handling to get, post * FRIDGE-461 Fixes * FRIDGE-461 Align errors * FRIDGE-461 Fix tests * FRIDGE-461 Fix test * FRIDGE-461 Fix sid * FRIDGE-461 Fix sid * FRIDGE-461 Fix sids Co-authored-by: Jared Szechy <[email protected]>
1 parent 9895f04 commit 33fe95f

File tree

4 files changed

+200
-22
lines changed

4 files changed

+200
-22
lines changed

lib/util/Constants.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,11 @@ const errors = [
137137
{ name: 'GATEWAY_DISCONNECTED', message: 'Connection to Twilio\'s servers was lost.' },
138138
{ name: 'INVALID_GATEWAY_MESSAGE', message: 'The JSON message received was malformed.' },
139139

140-
{ name: 'TASKROUTER_ERROR', message: 'TaskRouter failed to complete the request.' }
140+
{ name: 'TASKROUTER_ERROR', message: 'TaskRouter failed to complete the request.' },
141+
{ name: 'REQUEST_INVALID', message: 'The provided request is invalid.' },
142+
{ name: 'NETWORK_ERROR', message: 'No response from the server.' },
143+
{ name: 'SERVER_ERROR', message: 'Server has experienced an issue performing the request.' },
144+
{ name: 'UNKNOWN_ERROR', message: 'An unknown error has occurred.' }
141145
];
142146

143147
export const twilioErrors = errors.reduce((errs, error) => {

lib/util/Request.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import _ from 'lodash';
22
import * as axios from 'axios';
33
import * as https from 'https';
44
import Configuration from './Configuration';
5-
import { DEFAULT_HTTP_TIMEOUT } from './Constants';
5+
import Logger from './Logger';
6+
import { DEFAULT_HTTP_TIMEOUT, twilioErrors as Errors } from './Constants';
67

78
const httpMethods = {
89
GET: 'GET',
@@ -49,6 +50,8 @@ export default class Request {
4950
},
5051
httpsAgent
5152
});
53+
54+
this._log = new Logger(`Request-${this._config.getLogIdentifier()}`, this._config._logLevel);
5255
}
5356

5457

@@ -86,6 +89,8 @@ export default class Request {
8689
headers
8790
}).then(response => {
8891
return Promise.resolve(response.data.payload);
92+
}).catch((error) => {
93+
this.handleError(error);
8994
});
9095
}
9196

@@ -117,6 +122,8 @@ export default class Request {
117122
}
118123
}).then(response => {
119124
return Promise.resolve(response.data.payload);
125+
}).catch((error) => {
126+
this.handleError(error);
120127
});
121128
}
122129

@@ -135,4 +142,29 @@ export default class Request {
135142
token: this._config.token
136143
});
137144
}
145+
146+
/**
147+
* @private
148+
* @param {Error} error
149+
* @return {void}
150+
*/
151+
handleError(error) {
152+
if (error.response) {
153+
// Server responded with an error status code (e.g. 4xx or 5xx)
154+
if (error.response.status < 500) {
155+
this._log.error('Request failed with ', error.response.status, error.response.data.message);
156+
throw Errors.REQUEST_INVALID.clone(`Request failed with status code ${error.response.status}. ${error.response.data.message}`);
157+
}
158+
this._log.error('Server Error:', error.response.status, error.response.data.message);
159+
throw Errors.SERVER_ERROR.clone(`Server responded with status code ${error.response.status}. ${error.response.data.message}`);
160+
} else if (error.request) {
161+
// No response received (e.g. network issue, timeout)
162+
this._log.error('Network Error:', error.message);
163+
throw Errors.NETWORK_ERROR.clone(`Network error has occurred. ${error.message}`);
164+
} else {
165+
// Setting up the request errored
166+
this._log.error('Error:', error.message);
167+
throw Errors.UNKNOWN_ERROR.clone(`Error: ${error.message}`);
168+
}
169+
}
138170
}

test/integration/spec/OutgoingTransfer.js

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,7 @@ describe('OutgoingTransfer', () => {
142142

143143
// canceling the same outgoing task again
144144
return transferredTask.transfers.outgoing.cancel().catch(err => {
145-
assert.equal(err.response.status, 400,
146-
envTwilio.getErrorMessage('400 not received when canceling same task twice', credentials.accountSid, credentials.multiTaskBobSid));
147-
148-
assert.equal(err.response.statusText, `Transfer ${canceledTransfer.sid} is already canceled . Cannot cancel transfer.`,
149-
envTwilio.getErrorMessage('400 error message content mismatch', credentials.accountSid, credentials.multiTaskBobSid));
150-
145+
assert.equal(err.message, `Request failed with status code 400. Transfer ${canceledTransfer.sid} is already canceled . Cannot cancel transfer.`, envTwilio.getErrorMessage('400 not received when canceling same task twice', credentials.accountSid, credentials.multiTaskBobSid));
151146
done();
152147
});
153148
});

test/unit/spec/util/Request.js

Lines changed: 161 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@ describe('Request', () => {
2323
sandbox.restore();
2424
});
2525

26+
const setUpSuccessfulResponse = (stub) => {
27+
const mockResponse = {
28+
data: {
29+
payload: 'someData'
30+
}
31+
};
32+
stub.resolves(Promise.resolve(mockResponse));
33+
};
34+
35+
const setUpErrorResponse = (stub, error) => {
36+
stub.rejects(error);
37+
};
38+
2639
describe('constructor', () => {
2740
it('should throw an error if configuration is invalid', () => {
2841
(() => {
@@ -39,20 +52,15 @@ describe('Request', () => {
3952
beforeEach(() => {
4053
request = new Request(config);
4154

42-
const mockResponse = {
43-
data: {
44-
payload: 'someData'
45-
}
46-
};
47-
4855
// set up the stub to check the headers and also mock the response, instead of using axios
49-
stub = sandbox.stub(request._postClient, 'post').resolves(Promise.resolve(mockResponse));
56+
stub = sandbox.stub(request._postClient, 'post');
5057

5158
// build the expected request body
5259
requestBody = request.buildRequest('POST', requestURL, requestParams);
5360
});
5461

5562
it('adds default headers', () => {
63+
setUpSuccessfulResponse(stub);
5664
return request.post(requestURL, requestParams, API_V1).then(() => {
5765
sinon.assert.calledWith(stub, config.EB_SERVER, requestBody, {
5866
headers: {
@@ -65,6 +73,7 @@ describe('Request', () => {
6573
// eslint-disable-next-line no-warning-comments
6674
// TODO FLEXSDK-2255: unskip this test once the versioning bug is fixed
6775
it.skip('adds object version to If-Match header', () => {
76+
setUpSuccessfulResponse(stub);
6877
const version = '1';
6978

7079
return request.post(requestURL, requestParams, API_V1, version).then(() => {
@@ -77,6 +86,76 @@ describe('Request', () => {
7786
});
7887
});
7988

89+
it('should throw request invalid error', async() => {
90+
const error = new Error();
91+
error.response = {
92+
status: 400,
93+
data: {
94+
message: 'Invalid request'
95+
},
96+
};
97+
98+
setUpErrorResponse(stub, error);
99+
const version = '1';
100+
101+
try {
102+
await request.post(requestURL, requestParams, API_V1, version);
103+
throw new Error('Expected an error to be thrown');
104+
} catch (thrownError) {
105+
thrownError.message.should.equal('Request failed with status code 400. Invalid request');
106+
}
107+
});
108+
109+
it('should throw server error', async() => {
110+
const error = new Error();
111+
error.response = {
112+
status: 500,
113+
data:
114+
{
115+
message: 'Unexpected server error'
116+
}
117+
};
118+
119+
setUpErrorResponse(stub, error);
120+
const version = '1';
121+
122+
try {
123+
await request.post(requestURL, requestParams, API_V1, version);
124+
throw new Error('Expected an error to be thrown');
125+
} catch (thrownError) {
126+
thrownError.message.should.equal('Server responded with status code 500. Unexpected server error');
127+
}
128+
});
129+
130+
it('should throw network error', async() => {
131+
const error = new Error('Timeout');
132+
error.request = true;
133+
134+
setUpErrorResponse(stub, error);
135+
const version = '1';
136+
137+
try {
138+
await request.post(requestURL, requestParams, API_V1, version);
139+
throw new Error('Expected an error to be thrown');
140+
} catch (thrownError) {
141+
thrownError.message.should.equal('Network error has occurred. Timeout');
142+
}
143+
});
144+
145+
it('should throw unknown error', async() => {
146+
const error = new Error('Something unexpected happened!');
147+
148+
setUpErrorResponse(stub, error);
149+
const version = '1';
150+
151+
try {
152+
await request.post(requestURL, requestParams, API_V1, version);
153+
throw new Error('Expected an error to be thrown');
154+
} catch (thrownError) {
155+
thrownError.message.should.equal('Error: Something unexpected happened!');
156+
}
157+
});
158+
80159
it('should throw an error if required parameters are missing', () => {
81160
(() => {
82161
request.post();
@@ -100,20 +179,15 @@ describe('Request', () => {
100179
beforeEach(() => {
101180
request = new Request(config);
102181

103-
const mockResponse = {
104-
data: {
105-
payload: 'someData'
106-
}
107-
};
108-
109182
// set up the stub to check the headers and also mock the response, instead of using axios
110-
stub = sandbox.stub(request._postClient, 'post').resolves(Promise.resolve(mockResponse));
183+
stub = sandbox.stub(request._postClient, 'post');
111184

112185
// build the expected request body
113186
requestBody = request.buildRequest('GET', requestURL, requestParams);
114187
});
115188

116189
it('add default headers', () => {
190+
setUpSuccessfulResponse(stub);
117191
return request.get(requestURL, API_V1, requestParams).then(() => {
118192
sinon.assert.calledWith(stub, config.EB_SERVER, requestBody, {
119193
headers: {
@@ -124,6 +198,7 @@ describe('Request', () => {
124198
});
125199

126200
it('add empty params object if no params given', () => {
201+
setUpSuccessfulResponse(stub);
127202
requestBody = request.buildRequest('GET', requestURL, {});
128203
return request.get(requestURL, API_V1).then(() => {
129204
sinon.assert.calledWith(stub, config.EB_SERVER, requestBody, {
@@ -134,7 +209,79 @@ describe('Request', () => {
134209
});
135210
});
136211

212+
it('should throw request invalid error', async() => {
213+
const error = new Error();
214+
error.response = {
215+
status: 400,
216+
data:
217+
{
218+
message: 'Invalid request'
219+
}
220+
};
221+
222+
setUpErrorResponse(stub, error);
223+
const version = '1';
224+
225+
try {
226+
await request.post(requestURL, requestParams, API_V1, version);
227+
throw new Error('Expected an error to be thrown');
228+
} catch (thrownError) {
229+
thrownError.message.should.equal('Request failed with status code 400. Invalid request');
230+
}
231+
});
232+
233+
it('should throw server error', async() => {
234+
const error = new Error();
235+
error.response = {
236+
status: 500,
237+
data:
238+
{
239+
message: 'Unexpected server error'
240+
}
241+
};
242+
243+
setUpErrorResponse(stub, error);
244+
const version = '1';
245+
246+
try {
247+
await request.post(requestURL, requestParams, API_V1, version);
248+
throw new Error('Expected an error to be thrown');
249+
} catch (thrownError) {
250+
thrownError.message.should.equal('Server responded with status code 500. Unexpected server error');
251+
}
252+
});
253+
254+
it('should throw network error', async() => {
255+
const error = new Error('Timeout');
256+
error.request = true;
257+
258+
setUpErrorResponse(stub, error);
259+
const version = '1';
260+
261+
try {
262+
await request.post(requestURL, requestParams, API_V1, version);
263+
throw new Error('Expected an error to be thrown');
264+
} catch (thrownError) {
265+
thrownError.message.should.equal('Network error has occurred. Timeout');
266+
}
267+
});
268+
269+
it('should throw unknown error', async() => {
270+
const error = new Error('Something unexpected happened!');
271+
272+
setUpErrorResponse(stub, error);
273+
const version = '1';
274+
275+
try {
276+
await request.post(requestURL, requestParams, API_V1, version);
277+
throw new Error('Expected an error to be thrown');
278+
} catch (thrownError) {
279+
thrownError.message.should.equal('Error: Something unexpected happened!');
280+
}
281+
});
282+
137283
it('should throw an error if required parameters are missing', () => {
284+
setUpSuccessfulResponse(stub);
138285
(() => {
139286
request.get();
140287
}).should.throw(/<string>url is a required parameter/);

0 commit comments

Comments
 (0)