summaryrefslogtreecommitdiffstats
path: root/test/close-and-destroy.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--test/close-and-destroy.js344
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' })
+ }
+ })
+})