Skip to content
This repository was archived by the owner on Mar 11, 2022. It is now read-only.

Commit f96de70

Browse files
committed
Add tests for timeouts in response phase
1 parent 60f0c37 commit f96de70

File tree

1 file changed

+154
-0
lines changed

1 file changed

+154
-0
lines changed

test/plugins/retry.js

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,38 @@ describe('Retry Plugin', function() {
860860
});
861861

862862
describe('retries on error', function() {
863+
function dataPhaseErrorMockServer(port, timesToError, successBody, lifetime, done) {
864+
// nock is unable to mock the timeout in the response phase so create a mock http server
865+
const http = require('http');
866+
var counter = 0;
867+
const mockServer = http.createServer({}, function(req, res) {
868+
counter++;
869+
console.log(`DPE MOCK SERVER: received request ${counter} for URL: ${req.url}`);
870+
if (counter <= timesToError) {
871+
res.writeHead(200, { 'Content-Type': 'application/json' });
872+
res.write(successBody);
873+
// We intentionally fail to end the request so that the client side will timeout in the data phase
874+
// res.end();
875+
} else {
876+
// respond successfully promptly
877+
res.end(successBody);
878+
}
879+
}).listen(port, '127.0.0.1');
880+
// set a timeout so we don't hang the test (mocha's timeout won't kill the http server)
881+
const serverTimeout = setTimeout(() => {
882+
console.log('DPE MOCK SERVER: timeout');
883+
if (mockServer.listening) {
884+
console.log('DPE MOCK SERVER: close requested from timeout');
885+
mockServer.close(() => done(new Error('Test server timeout; client hang!')));
886+
}
887+
}, lifetime);
888+
return {server: mockServer,
889+
close: () => {
890+
clearTimeout(serverTimeout);
891+
mockServer.close(done);
892+
}};
893+
}
894+
863895
describe('with callback only', function() {
864896
it('performs request and returns response', function(done) {
865897
// NOTE: Use NOCK_OFF=true to test using a real CouchDB instance.
@@ -945,6 +977,31 @@ describe('Retry Plugin', function() {
945977
done();
946978
});
947979
});
980+
981+
it('does not retry in response data phase', function(done) {
982+
// mock server on 6984 that errors for 1 request then returns the DB info block for other reqs
983+
// and finally stops listening after 1s if it wasn't already closed
984+
const mock = dataPhaseErrorMockServer(6984, 1, '{"doc_count":0}', 1000, done);
985+
// A client that will timeout after 100 ms and make only 2 attempts with a retry 10 ms after receiving an error
986+
var cloudantClient = new Client({
987+
https: false,
988+
maxAttempt: 2,
989+
requestDefaults: {timeout: 100},
990+
plugins: { retry: { retryInitialDelayMsecs: 10, retryStatusCodes: [] } }
991+
});
992+
993+
var req = {
994+
url: 'http://127.0.0.1:6984/foo',
995+
auth: { username: ME, password: PASSWORD },
996+
method: 'GET'
997+
};
998+
999+
cloudantClient.request(req, function(err, resp, data) {
1000+
assert.ok(err, 'Should get called back with an error.');
1001+
assert.equal('ESOCKETTIMEDOUT', err.code);
1002+
mock.close();
1003+
});
1004+
});
9481005
});
9491006

9501007
describe('with listener only', function() {
@@ -1084,6 +1141,54 @@ describe('Retry Plugin', function() {
10841141
assert.fail('Unexpected data from server');
10851142
});
10861143
});
1144+
1145+
it('does not retry in response data phase', function(done) {
1146+
// mock server on 6985 that errors for 1 request then returns the DB info block for other reqs
1147+
// and finally stops listening after 1s if it wasn't already closed
1148+
const mock = dataPhaseErrorMockServer(6985, 1, '{"doc_count":0}', 1000, done);
1149+
1150+
// A client that will timeout after 100 ms and make only 2 attempts with a retry 10 ms after receiving an error
1151+
var cloudantClient = new Client({
1152+
https: false,
1153+
maxAttempt: 2,
1154+
requestDefaults: {timeout: 100},
1155+
plugins: { retry: { retryInitialDelayMsecs: 10, retryStatusCodes: [] } }
1156+
});
1157+
1158+
var req = {
1159+
url: 'http://127.0.0.1:6985/foo',
1160+
auth: { username: ME, password: PASSWORD },
1161+
method: 'GET'
1162+
};
1163+
1164+
var responseCount = 0;
1165+
var errors = [];
1166+
var responseData = '';
1167+
1168+
cloudantClient.request(req)
1169+
.on('error', (err) => {
1170+
errors.push(err);
1171+
})
1172+
.on('response', function(resp) {
1173+
responseCount++;
1174+
assert.equal(resp.statusCode, 200);
1175+
})
1176+
.on('data', function(data) {
1177+
responseData += data;
1178+
})
1179+
.on('end', function() {
1180+
let expectedErrors = 1;
1181+
if (process.version.startsWith('v16.')) {
1182+
// Node 16 has an additional `aborted` error
1183+
// https://github.com/nodejs/node/issues/28172
1184+
expectedErrors++;
1185+
}
1186+
assert.ok(responseData.toString('utf8').indexOf('"doc_count":0') > -1);
1187+
assert.equal(responseCount, 1);
1188+
assert.equal(errors.length, expectedErrors);
1189+
mock.close(done);
1190+
});
1191+
});
10871192
});
10881193

10891194
describe('with callback and listener', function() {
@@ -1235,6 +1340,55 @@ describe('Retry Plugin', function() {
12351340
assert.fail('Unexpected data from server');
12361341
});
12371342
});
1343+
1344+
it('does not retry in response data phase', function(done) {
1345+
// mock server on 6986 that errors for 1 request then returns the DB info block for other reqs
1346+
// and finally stops listening after 1s if it wasn't already closed
1347+
const mock = dataPhaseErrorMockServer(6986, 1, '{"doc_count":0}', 1000, done);
1348+
// A client that will timeout after 100 ms and make only 2 attempts with a retry 10 ms after receiving an error
1349+
var cloudantClient = new Client({
1350+
https: false,
1351+
maxAttempt: 2,
1352+
requestDefaults: {timeout: 100},
1353+
plugins: { retry: { retryInitialDelayMsecs: 10, retryStatusCodes: [] } }
1354+
});
1355+
1356+
var req = {
1357+
url: 'http://127.0.0.1:6986/foo',
1358+
auth: { username: ME, password: PASSWORD },
1359+
method: 'GET'
1360+
};
1361+
1362+
var responseCount = 0;
1363+
var errors = [];
1364+
var responseData = '';
1365+
cloudantClient.request(req, function(err, resp, data) {
1366+
assert.ok(err, 'Should get called back with an error.');
1367+
assert.equal('ESOCKETTIMEDOUT', err.code);
1368+
mock.close();
1369+
})
1370+
.on('error', (err) => {
1371+
errors.push(err);
1372+
})
1373+
.on('response', function(resp) {
1374+
responseCount++;
1375+
assert.equal(resp.statusCode, 200);
1376+
})
1377+
.on('data', function(data) {
1378+
responseData += data;
1379+
})
1380+
.on('end', function() {
1381+
let expectedErrors = 1;
1382+
if (process.version.startsWith('v16.')) {
1383+
// Node 16 has an additional `aborted` error
1384+
// https://github.com/nodejs/node/issues/28172
1385+
expectedErrors++;
1386+
}
1387+
assert.ok(responseData.toString('utf8').indexOf('"doc_count":0') > -1);
1388+
assert.equal(responseCount, 1);
1389+
assert.equal(errors.length, expectedErrors);
1390+
});
1391+
});
12381392
});
12391393
});
12401394
});

0 commit comments

Comments
 (0)