diff options
Diffstat (limited to 'test/client-dispatch.js')
-rw-r--r-- | test/client-dispatch.js | 815 |
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 () {} + }) + }) +}) |