diff options
Diffstat (limited to '')
-rw-r--r-- | test/close-and-destroy.js | 344 |
1 files changed, 344 insertions, 0 deletions
diff --git a/test/close-and-destroy.js b/test/close-and-destroy.js new file mode 100644 index 0000000..bd50ebb --- /dev/null +++ b/test/close-and-destroy.js @@ -0,0 +1,344 @@ +'use strict' + +const { test } = require('tap') +const { Client, errors } = require('..') +const { createServer } = require('http') +const { kSocket, kSize } = require('../lib/core/symbols') + +test('close waits for queued requests to finish', (t) => { + t.plan(16) + + const server = createServer() + + server.on('request', (req, res) => { + t.pass('request received') + res.end('hello') + }) + t.teardown(server.close.bind(server)) + + server.listen(0, () => { + const client = new Client(`http://localhost:${server.address().port}`) + t.teardown(client.destroy.bind(client)) + + client.request({ path: '/', method: 'GET' }, function (err, data) { + onRequest(err, data) + + client.request({ path: '/', method: 'GET' }, onRequest) + client.request({ path: '/', method: 'GET' }, onRequest) + client.request({ path: '/', method: 'GET' }, onRequest) + + // needed because the next element in the queue will be called + // after the current function completes + process.nextTick(function () { + client.close() + }) + }) + }) + + function onRequest (err, { statusCode, headers, body }) { + t.error(err) + t.equal(statusCode, 200) + const bufs = [] + body.on('data', (buf) => { + bufs.push(buf) + }) + body.on('end', () => { + t.equal('hello', Buffer.concat(bufs).toString('utf8')) + }) + } +}) + +test('destroy invoked all pending callbacks', (t) => { + t.plan(4) + + const server = createServer() + + server.on('request', (req, res) => { + res.write('hello') + }) + t.teardown(server.close.bind(server)) + + server.listen(0, () => { + const client = new Client(`http://localhost:${server.address().port}`, { + pipelining: 2 + }) + t.teardown(client.destroy.bind(client)) + + client.request({ path: '/', method: 'GET' }, (err, data) => { + t.error(err) + data.body.on('error', (err) => { + t.ok(err) + }).resume() + client.destroy() + }) + client.request({ path: '/', method: 'GET' }, (err) => { + t.type(err, errors.ClientDestroyedError) + }) + client.request({ path: '/', method: 'GET' }, (err) => { + t.type(err, errors.ClientDestroyedError) + }) + }) +}) + +test('destroy invoked all pending callbacks ticked', (t) => { + t.plan(4) + + const server = createServer() + + server.on('request', (req, res) => { + res.write('hello') + }) + t.teardown(server.close.bind(server)) + + server.listen(0, () => { + const client = new Client(`http://localhost:${server.address().port}`, { + pipelining: 2 + }) + t.teardown(client.destroy.bind(client)) + + let ticked = false + client.request({ path: '/', method: 'GET' }, (err) => { + t.equal(ticked, true) + t.type(err, errors.ClientDestroyedError) + }) + client.request({ path: '/', method: 'GET' }, (err) => { + t.equal(ticked, true) + t.type(err, errors.ClientDestroyedError) + }) + client.destroy() + ticked = true + }) +}) + +test('close waits until socket is destroyed', (t) => { + t.plan(4) + + const server = createServer((req, res) => { + res.end(req.url) + }) + t.teardown(server.close.bind(server)) + + server.listen(0, () => { + const client = new Client(`http://localhost:${server.address().port}`) + t.teardown(client.destroy.bind(client)) + + makeRequest() + + client.once('connect', () => { + let done = false + client[kSocket].on('close', () => { + done = true + }) + client.close((err) => { + t.error(err) + t.equal(client.closed, true) + t.equal(done, true) + }) + }) + + function makeRequest () { + client.request({ path: '/', method: 'GET' }, (err, data) => { + t.error(err) + }) + return client[kSize] <= client.pipelining + } + }) +}) + +test('close should still reconnect', (t) => { + t.plan(6) + + const server = createServer((req, res) => { + res.end(req.url) + }) + t.teardown(server.close.bind(server)) + + server.listen(0, () => { + const client = new Client(`http://localhost:${server.address().port}`) + t.teardown(client.destroy.bind(client)) + + t.ok(makeRequest()) + t.ok(!makeRequest()) + + client.close((err) => { + t.error(err) + t.equal(client.closed, true) + }) + client.once('connect', () => { + client[kSocket].destroy() + }) + + function makeRequest () { + client.request({ path: '/', method: 'GET' }, (err, data) => { + t.error(err) + data.body.resume() + }) + return client[kSize] <= client.pipelining + } + }) +}) + +test('close should call callback once finished', (t) => { + t.plan(6) + + const server = createServer((req, res) => { + setImmediate(function () { + res.end(req.url) + }) + }) + t.teardown(server.close.bind(server)) + + server.listen(0, () => { + const client = new Client(`http://localhost:${server.address().port}`) + t.teardown(client.destroy.bind(client)) + + t.ok(makeRequest()) + t.ok(!makeRequest()) + + client.close((err) => { + t.error(err) + t.equal(client.closed, true) + }) + + function makeRequest () { + client.request({ path: '/', method: 'GET' }, (err, data) => { + t.error(err) + data.body.resume() + }) + return client[kSize] <= client.pipelining + } + }) +}) + +test('closed and destroyed errors', (t) => { + t.plan(4) + + const client = new Client('http://localhost:4000') + t.teardown(client.destroy.bind(client)) + + client.request({ path: '/', method: 'GET' }, (err) => { + t.ok(err) + }) + client.close((err) => { + t.error(err) + }) + client.request({ path: '/', method: 'GET' }, (err) => { + t.type(err, errors.ClientClosedError) + client.destroy() + client.request({ path: '/', method: 'GET' }, (err) => { + t.type(err, errors.ClientDestroyedError) + }) + }) +}) + +test('close after and destroy should error', (t) => { + t.plan(2) + + const client = new Client('http://localhost:4000') + t.teardown(client.destroy.bind(client)) + + client.destroy() + client.close((err) => { + t.type(err, errors.ClientDestroyedError) + }) + client.close().catch((err) => { + t.type(err, errors.ClientDestroyedError) + }) +}) + +test('close socket and reconnect after maxRequestsPerClient reached', (t) => { + t.plan(5) + + const server = createServer((req, res) => { + res.end(req.url) + }) + + t.teardown(server.close.bind(server)) + + server.listen(0, async () => { + let connections = 0 + server.on('connection', () => { + connections++ + }) + const client = new Client( + `http://localhost:${server.address().port}`, + { maxRequestsPerClient: 2 } + ) + t.teardown(client.destroy.bind(client)) + + await t.resolves(makeRequest()) + await t.resolves(makeRequest()) + await t.resolves(makeRequest()) + await t.resolves(makeRequest()) + t.equal(connections, 2) + + function makeRequest () { + return client.request({ path: '/', method: 'GET' }) + } + }) +}) + +test('close socket and reconnect after maxRequestsPerClient reached (async)', (t) => { + t.plan(2) + + const server = createServer((req, res) => { + res.end(req.url) + }) + + t.teardown(server.close.bind(server)) + + server.listen(0, async () => { + let connections = 0 + server.on('connection', () => { + connections++ + }) + const client = new Client( + `http://localhost:${server.address().port}`, + { maxRequestsPerClient: 2 } + ) + t.teardown(client.destroy.bind(client)) + + await t.resolves( + Promise.all([ + makeRequest(), + makeRequest(), + makeRequest(), + makeRequest() + ]) + ) + t.equal(connections, 2) + + function makeRequest () { + return client.request({ path: '/', method: 'GET' }) + } + }) +}) + +test('should not close socket when no maxRequestsPerClient is provided', (t) => { + t.plan(5) + + const server = createServer((req, res) => { + res.end(req.url) + }) + + t.teardown(server.close.bind(server)) + + server.listen(0, async () => { + let connections = 0 + server.on('connection', () => { + connections++ + }) + const client = new Client(`http://localhost:${server.address().port}`) + t.teardown(client.destroy.bind(client)) + + await t.resolves(makeRequest()) + await t.resolves(makeRequest()) + await t.resolves(makeRequest()) + await t.resolves(makeRequest()) + t.equal(connections, 1) + + function makeRequest () { + return client.request({ path: '/', method: 'GET' }) + } + }) +}) |