summaryrefslogtreecommitdiffstats
path: root/test/client-dispatch.js
diff options
context:
space:
mode:
Diffstat (limited to 'test/client-dispatch.js')
-rw-r--r--test/client-dispatch.js815
1 files changed, 815 insertions, 0 deletions
diff --git a/test/client-dispatch.js b/test/client-dispatch.js
new file mode 100644
index 0000000..c3de37a
--- /dev/null
+++ b/test/client-dispatch.js
@@ -0,0 +1,815 @@
+'use strict'
+
+const { test } = require('tap')
+const http = require('http')
+const { Client, Pool, errors } = require('..')
+const stream = require('stream')
+
+test('dispatch invalid opts', (t) => {
+ t.plan(14)
+
+ const client = new Client('http://localhost:5000')
+
+ try {
+ client.dispatch({
+ path: '/',
+ method: 'GET',
+ upgrade: 1
+ }, null)
+ } catch (err) {
+ t.type(err, errors.InvalidArgumentError)
+ t.equal(err.message, 'handler must be an object')
+ }
+
+ try {
+ client.dispatch({
+ path: '/',
+ method: 'GET',
+ upgrade: 1
+ }, 'asd')
+ } catch (err) {
+ t.type(err, errors.InvalidArgumentError)
+ t.equal(err.message, 'handler must be an object')
+ }
+
+ client.dispatch({
+ path: '/',
+ method: 'GET',
+ upgrade: 1
+ }, {
+ onError (err) {
+ t.type(err, errors.InvalidArgumentError)
+ t.equal(err.message, 'upgrade must be a string')
+ }
+ })
+
+ client.dispatch({
+ path: '/',
+ method: 'GET',
+ headersTimeout: 'asd'
+ }, {
+ onError (err) {
+ t.type(err, errors.InvalidArgumentError)
+ t.equal(err.message, 'invalid headersTimeout')
+ }
+ })
+
+ client.dispatch({
+ path: '/',
+ method: 'GET',
+ bodyTimeout: 'asd'
+ }, {
+ onError (err) {
+ t.type(err, errors.InvalidArgumentError)
+ t.equal(err.message, 'invalid bodyTimeout')
+ }
+ })
+
+ client.dispatch({
+ origin: 'another',
+ path: '/',
+ method: 'GET',
+ bodyTimeout: 'asd'
+ }, {
+ onError (err) {
+ t.type(err, errors.InvalidArgumentError)
+ t.equal(err.message, 'invalid bodyTimeout')
+ }
+ })
+
+ client.dispatch(null, {
+ onError (err) {
+ t.type(err, errors.InvalidArgumentError)
+ t.equal(err.message, 'opts must be an object.')
+ }
+ })
+})
+
+test('basic dispatch get', (t) => {
+ t.plan(11)
+
+ const server = http.createServer((req, res) => {
+ t.equal('/', req.url)
+ t.equal('GET', req.method)
+ t.equal(`localhost:${server.address().port}`, req.headers.host)
+ t.equal(undefined, req.headers.foo)
+ t.equal('bar', req.headers.bar)
+ t.equal('', req.headers.baz)
+ t.equal(undefined, req.headers['content-length'])
+ res.end('hello')
+ })
+ t.teardown(server.close.bind(server))
+
+ const reqHeaders = {
+ foo: undefined,
+ bar: 'bar',
+ baz: null
+ }
+
+ server.listen(0, () => {
+ const client = new Client(`http://localhost:${server.address().port}`)
+ t.teardown(client.close.bind(client))
+
+ const bufs = []
+ client.dispatch({
+ path: '/',
+ method: 'GET',
+ headers: reqHeaders
+ }, {
+ onConnect () {
+ },
+ onHeaders (statusCode, headers) {
+ t.equal(statusCode, 200)
+ t.equal(Array.isArray(headers), true)
+ },
+ onData (buf) {
+ bufs.push(buf)
+ },
+ onComplete (trailers) {
+ t.same(trailers, [])
+ t.equal('hello', Buffer.concat(bufs).toString('utf8'))
+ },
+ onError () {
+ t.fail()
+ }
+ })
+ })
+})
+
+test('trailers dispatch get', (t) => {
+ t.plan(12)
+
+ const server = http.createServer((req, res) => {
+ t.equal('/', req.url)
+ t.equal('GET', req.method)
+ t.equal(`localhost:${server.address().port}`, req.headers.host)
+ t.equal(undefined, req.headers.foo)
+ t.equal('bar', req.headers.bar)
+ t.equal(undefined, req.headers['content-length'])
+ res.addTrailers({ 'Content-MD5': 'test' })
+ res.setHeader('Content-Type', 'text/plain')
+ res.setHeader('Trailer', 'Content-MD5')
+ res.end('hello')
+ })
+ t.teardown(server.close.bind(server))
+
+ const reqHeaders = {
+ foo: undefined,
+ bar: 'bar'
+ }
+
+ server.listen(0, () => {
+ const client = new Client(`http://localhost:${server.address().port}`)
+ t.teardown(client.close.bind(client))
+
+ const bufs = []
+ client.dispatch({
+ path: '/',
+ method: 'GET',
+ headers: reqHeaders
+ }, {
+ onConnect () {
+ },
+ onHeaders (statusCode, headers) {
+ t.equal(statusCode, 200)
+ t.equal(Array.isArray(headers), true)
+ {
+ const contentTypeIdx = headers.findIndex(x => x.toString() === 'Content-Type')
+ t.equal(headers[contentTypeIdx + 1].toString(), 'text/plain')
+ }
+ },
+ onData (buf) {
+ bufs.push(buf)
+ },
+ onComplete (trailers) {
+ t.equal(Array.isArray(trailers), true)
+ {
+ const contentMD5Idx = trailers.findIndex(x => x.toString() === 'Content-MD5')
+ t.equal(trailers[contentMD5Idx + 1].toString(), 'test')
+ }
+ t.equal('hello', Buffer.concat(bufs).toString('utf8'))
+ },
+ onError () {
+ t.fail()
+ }
+ })
+ })
+})
+
+test('dispatch onHeaders error', (t) => {
+ t.plan(1)
+
+ const server = http.createServer((req, res) => {
+ res.end()
+ })
+ t.teardown(server.close.bind(server))
+
+ server.listen(0, () => {
+ const client = new Client(`http://localhost:${server.address().port}`)
+ t.teardown(client.close.bind(client))
+
+ const _err = new Error()
+ client.dispatch({
+ path: '/',
+ method: 'GET'
+ }, {
+ onConnect () {
+ },
+ onHeaders (statusCode, headers) {
+ throw _err
+ },
+ onData (buf) {
+ t.fail()
+ },
+ onComplete (trailers) {
+ t.fail()
+ },
+ onError (err) {
+ t.equal(err, _err)
+ }
+ })
+ })
+})
+
+test('dispatch onComplete error', (t) => {
+ t.plan(2)
+
+ const server = http.createServer((req, res) => {
+ res.end()
+ })
+ t.teardown(server.close.bind(server))
+
+ server.listen(0, () => {
+ const client = new Client(`http://localhost:${server.address().port}`)
+ t.teardown(client.close.bind(client))
+
+ const _err = new Error()
+ client.dispatch({
+ path: '/',
+ method: 'GET'
+ }, {
+ onConnect () {
+ },
+ onHeaders (statusCode, headers) {
+ t.pass()
+ },
+ onData (buf) {
+ t.fail()
+ },
+ onComplete (trailers) {
+ throw _err
+ },
+ onError (err) {
+ t.equal(err, _err)
+ }
+ })
+ })
+})
+
+test('dispatch onData error', (t) => {
+ t.plan(2)
+
+ const server = http.createServer((req, res) => {
+ res.end('ad')
+ })
+ t.teardown(server.close.bind(server))
+
+ server.listen(0, () => {
+ const client = new Client(`http://localhost:${server.address().port}`)
+ t.teardown(client.close.bind(client))
+
+ const _err = new Error()
+ client.dispatch({
+ path: '/',
+ method: 'GET'
+ }, {
+ onConnect () {
+ },
+ onHeaders (statusCode, headers) {
+ t.pass()
+ },
+ onData (buf) {
+ throw _err
+ },
+ onComplete (trailers) {
+ t.fail()
+ },
+ onError (err) {
+ t.equal(err, _err)
+ }
+ })
+ })
+})
+
+test('dispatch onConnect error', (t) => {
+ t.plan(1)
+
+ const server = http.createServer((req, res) => {
+ res.end('ad')
+ })
+ t.teardown(server.close.bind(server))
+
+ server.listen(0, () => {
+ const client = new Client(`http://localhost:${server.address().port}`)
+ t.teardown(client.close.bind(client))
+
+ const _err = new Error()
+ client.dispatch({
+ path: '/',
+ method: 'GET'
+ }, {
+ onConnect () {
+ throw _err
+ },
+ onHeaders (statusCode, headers) {
+ t.fail()
+ },
+ onData (buf) {
+ t.fail()
+ },
+ onComplete (trailers) {
+ t.fail()
+ },
+ onError (err) {
+ t.equal(err, _err)
+ }
+ })
+ })
+})
+
+test('connect call onUpgrade once', (t) => {
+ t.plan(2)
+
+ const server = http.createServer((c) => {
+ t.fail()
+ })
+ server.on('connect', (req, socket, firstBodyChunk) => {
+ socket.write('HTTP/1.1 200 Connection established\r\n\r\n')
+
+ let data = firstBodyChunk.toString()
+ socket.on('data', (buf) => {
+ data += buf.toString()
+ })
+
+ socket.on('end', () => {
+ socket.end(data)
+ })
+ })
+ t.teardown(server.close.bind(server))
+
+ server.listen(0, async () => {
+ const client = new Client(`http://localhost:${server.address().port}`)
+ t.teardown(client.close.bind(client))
+
+ let recvData = ''
+ let count = 0
+ client.dispatch({
+ method: 'CONNECT',
+ path: '/'
+ }, {
+ onConnect () {
+ },
+ onHeaders (statusCode, headers) {
+ t.pass('should not throw')
+ },
+ onUpgrade (statusCode, headers, socket) {
+ t.equal(count++, 0)
+
+ socket.on('data', (d) => {
+ recvData += d
+ })
+
+ socket.on('end', () => {
+ t.equal(recvData.toString(), 'Body')
+ })
+
+ socket.write('Body')
+ socket.end()
+ },
+ onData (buf) {
+ t.fail()
+ },
+ onComplete (trailers) {
+ t.fail()
+ },
+ onError () {
+ t.fail()
+ }
+ })
+ })
+})
+
+test('dispatch onConnect missing', (t) => {
+ t.plan(1)
+
+ const server = http.createServer((req, res) => {
+ res.end('ad')
+ })
+ t.teardown(server.close.bind(server))
+
+ server.listen(0, () => {
+ const client = new Client(`http://localhost:${server.address().port}`)
+ t.teardown(client.close.bind(client))
+
+ client.dispatch({
+ path: '/',
+ method: 'GET'
+ }, {
+ onHeaders (statusCode, headers) {
+ t.pass('should not throw')
+ },
+ onData (buf) {
+ t.pass('should not throw')
+ },
+ onComplete (trailers) {
+ t.pass('should not throw')
+ },
+ onError (err) {
+ t.equal(err.code, 'UND_ERR_INVALID_ARG')
+ }
+ })
+ })
+})
+
+test('dispatch onHeaders missing', (t) => {
+ t.plan(1)
+
+ const server = http.createServer((req, res) => {
+ res.end('ad')
+ })
+ t.teardown(server.close.bind(server))
+
+ server.listen(0, () => {
+ const client = new Client(`http://localhost:${server.address().port}`)
+ t.teardown(client.close.bind(client))
+
+ client.dispatch({
+ path: '/',
+ method: 'GET'
+ }, {
+ onConnect () {
+ },
+ onData (buf) {
+ t.fail('should not throw')
+ },
+ onComplete (trailers) {
+ t.fail('should not throw')
+ },
+ onError (err) {
+ t.equal(err.code, 'UND_ERR_INVALID_ARG')
+ }
+ })
+ })
+})
+
+test('dispatch onData missing', (t) => {
+ t.plan(1)
+
+ const server = http.createServer((req, res) => {
+ res.end('ad')
+ })
+ t.teardown(server.close.bind(server))
+
+ server.listen(0, () => {
+ const client = new Client(`http://localhost:${server.address().port}`)
+ t.teardown(client.close.bind(client))
+
+ client.dispatch({
+ path: '/',
+ method: 'GET'
+ }, {
+ onConnect () {
+ },
+ onHeaders (statusCode, headers) {
+ t.fail('should not throw')
+ },
+ onComplete (trailers) {
+ t.fail('should not throw')
+ },
+ onError (err) {
+ t.equal(err.code, 'UND_ERR_INVALID_ARG')
+ }
+ })
+ })
+})
+
+test('dispatch onComplete missing', (t) => {
+ t.plan(1)
+
+ const server = http.createServer((req, res) => {
+ res.end('ad')
+ })
+ t.teardown(server.close.bind(server))
+
+ server.listen(0, () => {
+ const client = new Client(`http://localhost:${server.address().port}`)
+ t.teardown(client.close.bind(client))
+
+ client.dispatch({
+ path: '/',
+ method: 'GET'
+ }, {
+ onConnect () {
+ },
+ onHeaders (statusCode, headers) {
+ t.fail()
+ },
+ onData (buf) {
+ t.fail()
+ },
+ onError (err) {
+ t.equal(err.code, 'UND_ERR_INVALID_ARG')
+ }
+ })
+ })
+})
+
+test('dispatch onError missing', (t) => {
+ t.plan(1)
+
+ const server = http.createServer((req, res) => {
+ res.end('ad')
+ })
+ t.teardown(server.close.bind(server))
+
+ server.listen(0, () => {
+ const client = new Client(`http://localhost:${server.address().port}`)
+ t.teardown(client.close.bind(client))
+
+ try {
+ client.dispatch({
+ path: '/',
+ method: 'GET'
+ }, {
+ onConnect () {
+ },
+ onHeaders (statusCode, headers) {
+ t.fail()
+ },
+ onData (buf) {
+ t.fail()
+ },
+ onComplete (trailers) {
+ t.fail()
+ }
+ })
+ } catch (err) {
+ t.equal(err.code, 'UND_ERR_INVALID_ARG')
+ }
+ })
+})
+
+test('dispatch CONNECT onUpgrade missing', (t) => {
+ t.plan(2)
+
+ const server = http.createServer((req, res) => {
+ res.end('ad')
+ })
+ 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.dispatch({
+ path: '/',
+ method: 'GET',
+ upgrade: 'Websocket'
+ }, {
+ onConnect () {
+ },
+ onHeaders (statusCode, headers) {
+ },
+ onError (err) {
+ t.equal(err.code, 'UND_ERR_INVALID_ARG')
+ t.equal(err.message, 'invalid onUpgrade method')
+ }
+ })
+ })
+})
+
+test('dispatch upgrade onUpgrade missing', (t) => {
+ t.plan(2)
+
+ const server = http.createServer((req, res) => {
+ res.end('ad')
+ })
+ t.teardown(server.close.bind(server))
+
+ server.listen(0, () => {
+ const client = new Client(`http://localhost:${server.address().port}`)
+ t.teardown(client.close.bind(client))
+
+ client.dispatch({
+ path: '/',
+ method: 'GET',
+ upgrade: 'Websocket'
+ }, {
+ onConnect () {
+ },
+ onHeaders (statusCode, headers) {
+ },
+ onError (err) {
+ t.equal(err.code, 'UND_ERR_INVALID_ARG')
+ t.equal(err.message, 'invalid onUpgrade method')
+ }
+ })
+ })
+})
+
+test('dispatch pool onError missing', (t) => {
+ t.plan(2)
+
+ const server = http.createServer((req, res) => {
+ res.end('ad')
+ })
+ t.teardown(server.close.bind(server))
+
+ server.listen(0, () => {
+ const client = new Pool(`http://localhost:${server.address().port}`)
+ t.teardown(client.close.bind(client))
+
+ try {
+ client.dispatch({
+ path: '/',
+ method: 'GET',
+ upgrade: 'Websocket'
+ }, {
+ })
+ } catch (err) {
+ t.equal(err.code, 'UND_ERR_INVALID_ARG')
+ t.equal(err.message, 'invalid onError method')
+ }
+ })
+})
+
+test('dispatch onBodySent not a function', (t) => {
+ t.plan(2)
+ const server = http.createServer((req, res) => {
+ res.end('ad')
+ })
+ t.teardown(server.close.bind(server))
+
+ server.listen(0, () => {
+ const client = new Pool(`http://localhost:${server.address().port}`)
+ t.teardown(client.close.bind(client))
+
+ client.dispatch({
+ path: '/',
+ method: 'GET'
+ }, {
+ onBodySent: '42',
+ onConnect () {},
+ onHeaders () {},
+ onData () {},
+ onError (err) {
+ t.equal(err.code, 'UND_ERR_INVALID_ARG')
+ t.equal(err.message, 'invalid onBodySent method')
+ }
+ })
+ })
+})
+
+test('dispatch onBodySent buffer', (t) => {
+ t.plan(3)
+
+ const server = http.createServer((req, res) => {
+ res.end('ad')
+ })
+ t.teardown(server.close.bind(server))
+
+ server.listen(0, () => {
+ const client = new Pool(`http://localhost:${server.address().port}`)
+ t.teardown(client.close.bind(client))
+ const body = 'hello 🚀'
+ client.dispatch({
+ path: '/',
+ method: 'POST',
+ body
+ }, {
+ onBodySent (chunk) {
+ t.equal(chunk.toString(), body)
+ },
+ onRequestSent () {
+ t.pass()
+ },
+ onError (err) {
+ throw err
+ },
+ onConnect () {},
+ onHeaders () {},
+ onData () {},
+ onComplete () {
+ t.pass()
+ }
+ })
+ })
+})
+
+test('dispatch onBodySent stream', (t) => {
+ t.plan(8)
+ const server = http.createServer((req, res) => {
+ res.end('ad')
+ })
+ t.teardown(server.close.bind(server))
+ const chunks = ['he', 'llo', 'world', '🚀']
+ const toSendBytes = chunks.reduce((a, b) => a + Buffer.byteLength(b), 0)
+ const body = stream.Readable.from(chunks)
+ server.listen(0, () => {
+ const client = new Pool(`http://localhost:${server.address().port}`)
+ t.teardown(client.close.bind(client))
+ let sentBytes = 0
+ let currentChunk = 0
+ client.dispatch({
+ path: '/',
+ method: 'POST',
+ body
+ }, {
+ onBodySent (chunk) {
+ t.equal(chunks[currentChunk++], chunk)
+ sentBytes += Buffer.byteLength(chunk)
+ },
+ onRequestSent () {
+ t.pass()
+ },
+ onError (err) {
+ throw err
+ },
+ onConnect () {},
+ onHeaders () {},
+ onData () {},
+ onComplete () {
+ t.equal(currentChunk, chunks.length)
+ t.equal(sentBytes, toSendBytes)
+ t.pass()
+ }
+ })
+ })
+})
+
+test('dispatch onBodySent async-iterable', (t) => {
+ const server = http.createServer((req, res) => {
+ res.end('ad')
+ })
+ t.teardown(server.close.bind(server))
+ const chunks = ['he', 'llo', 'world', '🚀']
+ const toSendBytes = chunks.reduce((a, b) => a + Buffer.byteLength(b), 0)
+ server.listen(0, () => {
+ const client = new Pool(`http://localhost:${server.address().port}`)
+ t.teardown(client.close.bind(client))
+ let sentBytes = 0
+ let currentChunk = 0
+ client.dispatch({
+ path: '/',
+ method: 'POST',
+ body: chunks
+ }, {
+ onBodySent (chunk) {
+ t.equal(chunks[currentChunk++], chunk)
+ sentBytes += Buffer.byteLength(chunk)
+ },
+ onError (err) {
+ throw err
+ },
+ onConnect () {},
+ onHeaders () {},
+ onData () {},
+ onComplete () {
+ t.equal(currentChunk, chunks.length)
+ t.equal(sentBytes, toSendBytes)
+ t.end()
+ }
+ })
+ })
+})
+
+test('dispatch onBodySent throws error', (t) => {
+ const server = http.createServer((req, res) => {
+ res.end('ended')
+ })
+ t.teardown(server.close.bind(server))
+
+ server.listen(0, () => {
+ const client = new Pool(`http://localhost:${server.address().port}`)
+ t.teardown(client.close.bind(client))
+ const body = 'hello'
+ client.dispatch({
+ path: '/',
+ method: 'POST',
+ body
+ }, {
+ onBodySent (chunk) {
+ throw new Error('fail')
+ },
+ onError (err) {
+ t.type(err, Error)
+ t.equal(err.message, 'fail')
+ t.end()
+ },
+ onConnect () {},
+ onHeaders () {},
+ onData () {},
+ onComplete () {}
+ })
+ })
+})