Skip to main content
Glama
index.test.js60.2 kB
'use strict' const { test } = require('node:test') const { Readable, finished, pipeline } = require('node:stream') const qs = require('node:querystring') const fs = require('node:fs') const zlib = require('node:zlib') const http = require('node:http') const eos = require('end-of-stream') const express = require('express') const multer = require('multer') const inject = require('../index') const parseURL = require('../lib/parse-url') const NpmFormData = require('form-data') const formAutoContent = require('form-auto-content') const httpMethods = [ 'delete', 'get', 'head', 'options', 'patch', 'post', 'put', 'trace' ] test('returns non-chunked payload', (t, done) => { t.plan(7) const output = 'example.com:8080|/hello' const dispatch = function (req, res) { res.statusMessage = 'Super' res.setHeader('x-extra', 'hello') res.writeHead(200, { 'Content-Type': 'text/plain', 'Content-Length': output.length }) res.end(req.headers.host + '|' + req.url) } inject(dispatch, 'http://example.com:8080/hello', (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.statusCode, 200) t.assert.strictEqual(res.statusMessage, 'Super') t.assert.ok(res.headers.date) t.assert.deepStrictEqual(res.headers, { date: res.headers.date, connection: 'keep-alive', 'x-extra': 'hello', 'content-type': 'text/plain', 'content-length': output.length.toString() }) t.assert.strictEqual(res.payload, output) t.assert.strictEqual(res.rawPayload.toString(), 'example.com:8080|/hello') done() }) }) test('returns single buffer payload', (t, done) => { t.plan(6) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.headers.host + '|' + req.url) } inject(dispatch, { url: 'http://example.com:8080/hello' }, (err, res) => { t.assert.ifError(err) t.assert.ok(res.headers.date) t.assert.ok(res.headers.connection) t.assert.strictEqual(res.headers['transfer-encoding'], 'chunked') t.assert.strictEqual(res.payload, 'example.com:8080|/hello') t.assert.strictEqual(res.rawPayload.toString(), 'example.com:8080|/hello') done() }) }) test('passes headers', (t, done) => { t.plan(2) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.headers.super) } inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello', headers: { Super: 'duper' } }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'duper') done() }) }) test('request has rawHeaders', (t, done) => { t.plan(3) const dispatch = function (req, res) { t.assert.ok(Array.isArray(req.rawHeaders)) t.assert.deepStrictEqual(req.rawHeaders, ['super', 'duper', 'user-agent', 'lightMyRequest', 'host', 'example.com:8080']) res.writeHead(200) res.end() } inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello', headers: { Super: 'duper' } }, (err) => { t.assert.ifError(err) done() }) }) test('request inherits from custom class', (t, done) => { t.plan(2) const dispatch = function (req, res) { t.assert.ok(req instanceof http.IncomingMessage) res.writeHead(200) res.end() } inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello', Request: http.IncomingMessage }, (err) => { t.assert.ifError(err) done() }) }) test('request with custom class preserves stream data', (t, done) => { t.plan(2) const dispatch = function (req, res) { t.assert.ok(req._readableState) res.writeHead(200) res.end() } inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello', Request: http.IncomingMessage }, (err) => { t.assert.ifError(err) done() }) }) test('assert Request option has a valid prototype', (t) => { t.plan(2) const dispatch = function (_req, res) { t.assert.ifError('should not get here') res.writeHead(500) res.end() } const MyInvalidRequest = {} t.assert.throws(() => { inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello', Request: MyInvalidRequest }, () => {}) }, Error) t.assert.throws(() => { inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello', Request: 'InvalidRequest' }, () => {}) }, Error) }) test('passes remote address', (t, done) => { t.plan(2) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.socket.remoteAddress) } inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello', remoteAddress: '1.2.3.4' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, '1.2.3.4') done() }) }) test('passes a socket which emits events like a normal one does', (t, done) => { t.plan(2) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) req.socket.on('timeout', () => {}) res.end('added') } inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'added') done() }) }) test('includes deprecated connection on request', (t, done) => { t.plan(3) const warnings = process.listeners('warning') process.removeAllListeners('warning') function onWarning (err) { t.assert.strictEqual(err.code, 'FST_LIGHTMYREQUEST_DEP01') return false } process.on('warning', onWarning) t.after(() => { process.removeListener('warning', onWarning) for (const fn of warnings) { process.on('warning', fn) } }) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.connection.remoteAddress) } inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello', remoteAddress: '1.2.3.4' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, '1.2.3.4') done() }) }) const parseQuery = url => { const parsedURL = parseURL(url) return qs.parse(parsedURL.search.slice(1)) } test('passes query', (t, done) => { t.plan(2) const query = { message: 'OK', xs: ['foo', 'bar'] } const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.url) } inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello', query }, (err, res) => { t.assert.ifError(err) t.assert.deepEqual(parseQuery(res.payload), query) done() }) }) test('query will be merged into that in url', (t, done) => { t.plan(2) const query = { xs: ['foo', 'bar'] } const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.url) } inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello?message=OK', query }, (err, res) => { t.assert.ifError(err) t.assert.deepEqual(parseQuery(res.payload), Object.assign({ message: 'OK' }, query)) done() }) }) test('passes query as a string', (t, done) => { t.plan(2) const query = 'message=OK&xs=foo&xs=bar' const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.url) } inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello', query }, (err, res) => { t.assert.ifError(err) t.assert.deepEqual(parseQuery(res.payload), { message: 'OK', xs: ['foo', 'bar'] }) done() }) }) test('query as a string will be merged into that in url', (t, done) => { t.plan(2) const query = 'xs=foo&xs=bar' const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.url) } inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello?message=OK', query }, (err, res) => { t.assert.ifError(err) t.assert.deepEqual(parseQuery(res.payload), Object.assign({ message: 'OK' }, { xs: ['foo', 'bar'] })) done() }) }) test('passes localhost as default remote address', (t, done) => { t.plan(2) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.socket.remoteAddress) } inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, '127.0.0.1') done() }) }) test('passes host option as host header', (t, done) => { t.plan(2) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.headers.host) } inject(dispatch, { method: 'GET', url: '/hello', headers: { host: 'test.example.com' } }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'test.example.com') done() }) }) test('passes localhost as default host header', (t, done) => { t.plan(2) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.headers.host) } inject(dispatch, { method: 'GET', url: '/hello' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'localhost:80') done() }) }) test('passes authority as host header', (t, done) => { t.plan(2) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.headers.host) } inject(dispatch, { method: 'GET', url: '/hello', authority: 'something' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'something') done() }) }) test('passes uri host as host header', (t, done) => { t.plan(2) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.headers.host) } inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'example.com:8080') done() }) }) test('includes default http port in host header', (t, done) => { t.plan(2) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.headers.host) } inject(dispatch, 'http://example.com', (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'example.com:80') done() }) }) test('includes default https port in host header', (t, done) => { t.plan(2) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.headers.host) } inject(dispatch, 'https://example.com', (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'example.com:443') done() }) }) test('optionally accepts an object as url', (t, done) => { t.plan(5) const output = 'example.com:8080|/hello?test=1234' const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain', 'Content-Length': output.length }) res.end(req.headers.host + '|' + req.url) } const url = { protocol: 'http', hostname: 'example.com', port: '8080', pathname: 'hello', query: { test: '1234' } } inject(dispatch, { url }, (err, res) => { t.assert.ifError(err) t.assert.ok(res.headers.date) t.assert.ok(res.headers.connection) t.assert.ifError(res.headers['transfer-encoding']) t.assert.strictEqual(res.payload, output) done() }) }) test('leaves user-agent unmodified', (t, done) => { t.plan(2) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.headers['user-agent']) } inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello', headers: { 'user-agent': 'duper' } }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'duper') done() }) }) test('returns chunked payload', (t, done) => { t.plan(5) const dispatch = function (_req, res) { res.writeHead(200, 'OK') res.write('a') res.write('b') res.end() } inject(dispatch, { method: 'GET', url: '/' }, (err, res) => { t.assert.ifError(err) t.assert.ok(res.headers.date) t.assert.ok(res.headers.connection) t.assert.strictEqual(res.headers['transfer-encoding'], 'chunked') t.assert.strictEqual(res.payload, 'ab') done() }) }) test('sets trailers in response object', (t, done) => { t.plan(4) const dispatch = function (_req, res) { res.setHeader('Trailer', 'Test') res.addTrailers({ Test: 123 }) res.end() } inject(dispatch, { method: 'GET', url: '/' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.headers.trailer, 'Test') t.assert.strictEqual(res.headers.test, undefined) t.assert.strictEqual(res.trailers.test, '123') done() }) }) test('parses zipped payload', (t, done) => { t.plan(4) const dispatch = function (_req, res) { res.writeHead(200, 'OK') const stream = fs.createReadStream('./package.json') stream.pipe(zlib.createGzip()).pipe(res) } inject(dispatch, { method: 'GET', url: '/' }, (err, res) => { t.assert.ifError(err) fs.readFile('./package.json', { encoding: 'utf-8' }, (err, file) => { t.assert.ifError(err) zlib.unzip(res.rawPayload, (err, unzipped) => { t.assert.ifError(err) t.assert.strictEqual(unzipped.toString('utf-8'), file) done() }) }) }) }) test('returns multi buffer payload', (t, done) => { t.plan(2) const dispatch = function (_req, res) { res.writeHead(200) res.write('a') res.write(Buffer.from('b')) res.end() } inject(dispatch, { method: 'GET', url: '/' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'ab') done() }) }) test('returns null payload', (t, done) => { t.plan(2) const dispatch = function (_req, res) { res.writeHead(200, { 'Content-Length': 0 }) res.end() } inject(dispatch, { method: 'GET', url: '/' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, '') done() }) }) test('allows ending twice', (t, done) => { t.plan(2) const dispatch = function (_req, res) { res.writeHead(200, { 'Content-Length': 0 }) res.end() res.end() } inject(dispatch, { method: 'GET', url: '/' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, '') done() }) }) test('identifies injection object', (t, done) => { t.plan(6) const dispatchRequest = function (req, res) { t.assert.strictEqual(inject.isInjection(req), true) t.assert.strictEqual(inject.isInjection(res), true) res.writeHead(200, { 'Content-Length': 0 }) res.end() } const dispatchCustomRequest = function (req, res) { t.assert.strictEqual(inject.isInjection(req), true) t.assert.strictEqual(inject.isInjection(res), true) res.writeHead(200, { 'Content-Length': 0 }) res.end() } const options = { method: 'GET', url: '/' } const cb = (err) => { t.assert.ifError(err) } const cbDone = (err) => { t.assert.ifError(err) done() } inject(dispatchRequest, options, cb) inject(dispatchCustomRequest, { ...options, Request: http.IncomingMessage }, cbDone) }) test('pipes response', (t, done) => { t.plan(3) let finished = false const dispatch = function (_req, res) { res.writeHead(200) const stream = getTestStream() res.on('finish', () => { finished = true }) stream.pipe(res) } inject(dispatch, { method: 'GET', url: '/' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(finished, true) t.assert.strictEqual(res.payload, 'hi') done() }) }) test('pipes response with old stream', (t, done) => { t.plan(3) let finished = false const dispatch = function (_req, res) { res.writeHead(200) const stream = getTestStream() stream.pause() const stream2 = new Readable().wrap(stream) stream.resume() res.on('finish', () => { finished = true }) stream2.pipe(res) } inject(dispatch, { method: 'GET', url: '/' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(finished, true) t.assert.strictEqual(res.payload, 'hi') done() }) }) test('echos object payload', (t, done) => { t.plan(3) const dispatch = function (req, res) { res.writeHead(200, { 'content-type': req.headers['content-type'] }) req.pipe(res) } inject(dispatch, { method: 'POST', url: '/test', payload: { a: 1 } }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.headers['content-type'], 'application/json') t.assert.strictEqual(res.payload, '{"a":1}') done() }) }) test('supports body option in Request and property in Response', (t, done) => { t.plan(3) const dispatch = function (req, res) { res.writeHead(200, { 'content-type': req.headers['content-type'] }) req.pipe(res) } inject(dispatch, { method: 'POST', url: '/test', body: { a: 1 } }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.headers['content-type'], 'application/json') t.assert.strictEqual(res.body, '{"a":1}') done() }) }) test('echos buffer payload', (t, done) => { t.plan(2) const dispatch = function (req, res) { res.writeHead(200) req.pipe(res) } inject(dispatch, { method: 'POST', url: '/test', payload: Buffer.from('test!') }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'test!') done() }) }) test('echos object payload with non-english utf-8 string', (t, done) => { t.plan(3) const dispatch = function (req, res) { res.writeHead(200, { 'content-type': req.headers['content-type'] }) req.pipe(res) } inject(dispatch, { method: 'POST', url: '/test', payload: { a: '½½א' } }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.headers['content-type'], 'application/json') t.assert.strictEqual(res.payload, '{"a":"½½א"}') done() }) }) test('echos object payload without payload', (t, done) => { t.plan(2) const dispatch = function (req, res) { res.writeHead(200) req.pipe(res) } inject(dispatch, { method: 'POST', url: '/test' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, '') done() }) }) test('retains content-type header', (t, done) => { t.plan(3) const dispatch = function (req, res) { res.writeHead(200, { 'content-type': req.headers['content-type'] }) req.pipe(res) } inject(dispatch, { method: 'POST', url: '/test', payload: { a: 1 }, headers: { 'content-type': 'something' } }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.headers['content-type'], 'something') t.assert.strictEqual(res.payload, '{"a":1}') done() }) }) test('adds a content-length header if none set when payload specified', (t, done) => { t.plan(2) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.headers['content-length']) } inject(dispatch, { method: 'POST', url: '/test', payload: { a: 1 } }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, '{"a":1}'.length.toString()) done() }) }) test('retains a content-length header when payload specified', (t, done) => { t.plan(2) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.headers['content-length']) } inject(dispatch, { method: 'POST', url: '/test', payload: '', headers: { 'content-length': '10' } }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, '10') done() }) }) test('can handle a stream payload', (t, done) => { t.plan(2) const dispatch = function (req, res) { readStream(req, (buff) => { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(buff) }) } inject(dispatch, { method: 'POST', url: '/', payload: getTestStream() }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'hi') done() }) }) test('can handle a stream payload that errors', (t, done) => { t.plan(2) const dispatch = function (req) { req.resume() } const payload = new Readable({ read () { this.destroy(new Error('kaboom')) } }) inject(dispatch, { method: 'POST', url: '/', payload }, (err) => { t.assert.ok(err) t.assert.equal(err.message, 'kaboom') done() }) }) test('can handle a stream payload of utf-8 strings', (t, done) => { t.plan(2) const dispatch = function (req, res) { readStream(req, (buff) => { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(buff) }) } inject(dispatch, { method: 'POST', url: '/', payload: getTestStream('utf8') }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'hi') done() }) }) test('can override stream payload content-length header', (t, done) => { t.plan(2) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.headers['content-length']) } const headers = { 'content-length': '100' } inject(dispatch, { method: 'POST', url: '/', payload: getTestStream(), headers }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, '100') done() }) }) test('writeHead returns single buffer payload', (t, done) => { t.plan(4) const reply = 'Hello World' const statusCode = 200 const statusMessage = 'OK' const dispatch = function (_req, res) { res.writeHead(statusCode, statusMessage, { 'Content-Type': 'text/plain', 'Content-Length': reply.length }) res.end(reply) } inject(dispatch, { method: 'GET', url: '/' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.statusCode, statusCode) t.assert.strictEqual(res.statusMessage, statusMessage) t.assert.strictEqual(res.payload, reply) done() }) }) test('_read() plays payload', (t, done) => { t.plan(2) const dispatch = function (req, res) { let buffer = '' req.on('readable', () => { buffer = buffer + (req.read() || '') }) req.on('close', () => { }) req.on('end', () => { res.writeHead(200, { 'Content-Length': 0 }) res.end(buffer) req.destroy() }) } const body = 'something special just for you' inject(dispatch, { method: 'GET', url: '/', payload: body }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, body) done() }) }) test('simulates split', (t, done) => { t.plan(2) const dispatch = function (req, res) { let buffer = '' req.on('readable', () => { buffer = buffer + (req.read() || '') }) req.on('close', () => { }) req.on('end', () => { res.writeHead(200, { 'Content-Length': 0 }) res.end(buffer) req.destroy() }) } const body = 'something special just for you' inject(dispatch, { method: 'GET', url: '/', payload: body, simulate: { split: true } }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, body) done() }) }) test('simulates error', (t, done) => { t.plan(2) const dispatch = function (req, res) { req.on('readable', () => { }) req.on('error', () => { res.writeHead(200, { 'Content-Length': 0 }) res.end('error') }) } const body = 'something special just for you' inject(dispatch, { method: 'GET', url: '/', payload: body, simulate: { error: true } }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'error') done() }) }) test('simulates no end without payload', (t, done) => { t.plan(2) let end = false const dispatch = function (req) { req.resume() req.on('end', () => { end = true }) } let replied = false inject(dispatch, { method: 'GET', url: '/', simulate: { end: false } }, () => { replied = true }) setTimeout(() => { t.assert.strictEqual(end, false) t.assert.strictEqual(replied, false) done() }, 10) }) test('simulates no end with payload', (t, done) => { t.plan(2) let end = false const dispatch = function (req) { req.resume() req.on('end', () => { end = true }) } let replied = false inject(dispatch, { method: 'GET', url: '/', payload: '1234567', simulate: { end: false } }, () => { replied = true }) setTimeout(() => { t.assert.strictEqual(end, false) t.assert.strictEqual(replied, false) done() }, 10) }) test('simulates close', (t, done) => { t.plan(2) const dispatch = function (req, res) { let buffer = '' req.on('readable', () => { buffer = buffer + (req.read() || '') }) req.on('close', () => { res.writeHead(200, { 'Content-Length': 0 }) res.end('close') }) req.on('end', () => { }) } const body = 'something special just for you' inject(dispatch, { method: 'GET', url: '/', payload: body, simulate: { close: true } }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'close') done() }) }) test('errors for invalid input options', (t) => { t.plan(1) t.assert.throws( () => inject({}, {}, () => {}), { name: 'AssertionError', message: 'dispatchFunc should be a function' } ) }) test('errors for missing url', (t) => { t.plan(1) t.assert.throws( () => inject(() => {}, {}, () => {}), { message: /must have required property 'url'/ } ) }) test('errors for an incorrect simulation object', (t) => { t.plan(1) t.assert.throws( () => inject(() => {}, { url: '/', simulate: 'sample string' }, () => {}), { message: /^must be object$/ } ) }) test('ignores incorrect simulation object', (t) => { t.plan(1) t.assert.doesNotThrow(() => inject(() => { }, { url: '/', simulate: 'sample string', validate: false }, () => { })) }) test('errors for an incorrect simulation object values', (t) => { t.plan(1) t.assert.throws( () => inject(() => {}, { url: '/', simulate: { end: 'wrong input' } }, () => {}), { message: /^must be boolean$/ } ) }) test('promises support', (t, done) => { t.plan(1) const dispatch = function (_req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end('hello') } inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello' }) .then(res => { t.assert.strictEqual(res.payload, 'hello') done() }) .catch(t.assert.fail) }) test('this should be the server instance', (t, done) => { t.plan(2) const server = http.createServer() const dispatch = function (_req, res) { t.assert.strictEqual(this, server) res.end('hello') } inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello', server }) .then(res => t.assert.strictEqual(res.statusCode, 200)) .catch(t.assert.fail) .finally(done) }) test('should handle response errors', (t, done) => { t.plan(1) const dispatch = function (_req, res) { res.connection.destroy(new Error('kaboom')) } inject(dispatch, 'http://example.com:8080/hello', (err) => { t.assert.ok(err) done() }) }) test('should handle response errors (promises)', async (t) => { t.plan(1) const dispatch = function (_req, res) { res.connection.destroy(new Error('kaboom')) } await t.assert.rejects( () => inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello' }), { name: 'Error', message: 'kaboom' } ) }) test('should handle response timeout handler', (t, done) => { t.plan(3) const dispatch = function (_req, res) { const handle = setTimeout(() => { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end('incorrect') }, 200) res.setTimeout(100, () => { clearTimeout(handle) res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end('correct') }) res.on('timeout', () => { t.assert.ok(true, 'Response timeout event not emitted') }) } inject(dispatch, { method: 'GET', url: '/test' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'correct') done() }) }) test('should throw on unknown HTTP method', (t) => { t.plan(1) const dispatch = function () { } t.assert.throws(() => inject(dispatch, { method: 'UNKNOWN_METHOD', url: 'http://example.com:8080/hello' }, (err, _res) => { t.assert.ok(err) }), Error) }) test('should throw on unknown HTTP method (promises)', (t) => { t.plan(1) const dispatch = function () { } t.assert.throws(() => inject(dispatch, { method: 'UNKNOWN_METHOD', url: 'http://example.com:8080/hello' }) .then(() => {}), Error) }) test('HTTP method is case insensitive', (t, done) => { t.plan(3) const dispatch = function (_req, res) { res.end('Hi!') } inject(dispatch, { method: 'get', url: 'http://example.com:8080/hello' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.statusCode, 200) t.assert.strictEqual(res.payload, 'Hi!') done() }) }) test('form-data should be handled correctly', (t, done) => { t.plan(4) const dispatch = function (req, res) { t.assert.strictEqual(req.headers['transfer-encoding'], undefined) let body = '' req.on('data', d => { body += d }) req.on('end', () => { res.end(body) }) } const form = new NpmFormData() form.append('my_field', 'my value') inject(dispatch, { method: 'POST', url: 'http://example.com:8080/hello', headers: { // Transfer-encoding is automatically deleted if Stream1 is used 'transfer-encoding': 'chunked' }, payload: form }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.statusCode, 200) t.assert.ok(/--.+\r\nContent-Disposition: form-data; name="my_field"\r\n\r\nmy value\r\n--.+--\r\n/.test(res.payload)) done() }) }) test('path as alias to url', (t, done) => { t.plan(2) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.url) } inject(dispatch, { method: 'GET', path: 'http://example.com:8080/hello' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, '/hello') done() }) }) test('Should throw if both path and url are missing', (t) => { t.plan(1) t.assert.throws( () => inject(() => {}, { method: 'GET' }, () => {}), { message: /must have required property 'url',must have required property 'path'/ } ) }) test('chainable api: backwards compatibility for promise (then)', (t, done) => { t.plan(1) const dispatch = function (_req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end('hello') } inject(dispatch) .get('/') .then(res => t.assert.strictEqual(res.payload, 'hello')) .catch(t.assert.fail) .finally(done) }) test('chainable api: backwards compatibility for promise (catch)', (t, done) => { t.plan(1) function dispatch () { throw Error } inject(dispatch) .get('/') .catch(err => t.assert.ok(err)) .finally(done) }) test('chainable api: multiple call of then should return the same promise', (t, done) => { t.plan(2) let id = 0 function dispatch (_req, res) { res.writeHead(200, { 'Content-Type': 'text/plain', 'Request-Id': id }) ++id t.assert.ok('request id incremented') res.end('hello') } const chain = inject(dispatch).get('/') chain.then(res => { chain.then(rep => { t.assert.strictEqual(res.headers['request-id'], rep.headers['request-id']) done() }) }) }) test('chainable api: http methods should work correctly', (t, done) => { t.plan(16) function dispatch (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.method) } httpMethods.forEach((method, index) => { inject(dispatch)[method]('http://example.com:8080/hello') .end((err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.body, method.toUpperCase()) if (index === httpMethods.length - 1) { done() } }) }) }) test('chainable api: http methods should throw if already invoked', (t, done) => { t.plan(8) function dispatch (_req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end() } httpMethods.forEach((method, index) => { const chain = inject(dispatch)[method]('http://example.com:8080/hello') chain.end() t.assert.throws(() => chain[method]('/'), Error) if (index === httpMethods.length - 1) { done() } }) }) test('chainable api: body method should work correctly', (t, done) => { t.plan(2) function dispatch (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) req.pipe(res) } inject(dispatch) .get('http://example.com:8080/hello') .body('test') .end((err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.body, 'test') done() }) }) test('chainable api: cookie', (t, done) => { t.plan(2) function dispatch (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.headers.cookie) } inject(dispatch) .get('http://example.com:8080/hello') .body('test') .cookies({ hello: 'world', fastify: 'rulez' }) .end((err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.body, 'hello=world; fastify=rulez') done() }) }) test('chainable api: body method should throw if already invoked', (t) => { t.plan(1) function dispatch (_req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end() } const chain = inject(dispatch) chain .get('http://example.com:8080/hello') .end() t.assert.throws(() => chain.body('test'), Error) }) test('chainable api: headers method should work correctly', (t, done) => { t.plan(2) function dispatch (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.headers.foo) } inject(dispatch) .get('http://example.com:8080/hello') .headers({ foo: 'bar' }) .end((err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'bar') done() }) }) test('chainable api: headers method should throw if already invoked', (t) => { t.plan(1) function dispatch (_req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end() } const chain = inject(dispatch) chain .get('http://example.com:8080/hello') .end() t.assert.throws(() => chain.headers({ foo: 'bar' }), Error) }) test('chainable api: payload method should work correctly', (t, done) => { t.plan(2) function dispatch (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) req.pipe(res) } inject(dispatch) .get('http://example.com:8080/hello') .payload('payload') .end((err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'payload') done() }) }) test('chainable api: payload method should throw if already invoked', (t) => { t.plan(1) function dispatch (_req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end() } const chain = inject(dispatch) chain .get('http://example.com:8080/hello') .end() t.assert.throws(() => chain.payload('payload'), Error) }) test('chainable api: query method should work correctly', (t, done) => { t.plan(2) const query = { message: 'OK', xs: ['foo', 'bar'] } function dispatch (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.url) } inject(dispatch) .get('http://example.com:8080/hello') .query(query) .end((err, res) => { t.assert.ifError(err) t.assert.deepEqual(parseQuery(res.payload), query) done() }) }) test('chainable api: query method should throw if already invoked', (t) => { t.plan(1) function dispatch (_req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end() } const chain = inject(dispatch) chain .get('http://example.com:8080/hello') .end() t.assert.throws(() => chain.query({ foo: 'bar' }), Error) }) test('chainable api: invoking end method after promise method should throw', (t) => { t.plan(1) function dispatch (_req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end() } const chain = inject(dispatch).get('http://example.com:8080/hello') chain.then() t.assert.throws(() => chain.end(), Error) }) test('chainable api: invoking promise method after end method with a callback function should throw', (t, done) => { t.plan(2) function dispatch (_req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end() } const chain = inject(dispatch).get('http://example.com:8080/hello') chain.end((err) => { t.assert.ifError(err) done() }) t.assert.throws(() => chain.then(), Error) }) test('chainable api: invoking promise method after end method without a callback function should work properly', (t, done) => { t.plan(1) function dispatch (_req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end('hello') } inject(dispatch) .get('http://example.com:8080/hello') .end() .then(res => t.assert.strictEqual(res.payload, 'hello')) .finally(done) }) test('chainable api: invoking end method multiple times should throw', (t) => { t.plan(1) function dispatch (_req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end() } const chain = inject(dispatch).get('http://example.com:8080/hello') chain.end() t.assert.throws(() => chain.end(), Error) }) test('chainable api: string url', (t, done) => { t.plan(2) function dispatch (_req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end() t.assert.ok('pass') } const chain = inject(dispatch, 'http://example.com:8080/hello') chain.then(() => t.assert.ok('pass')).finally(done) }) test('Response.json() should parse the JSON payload', (t, done) => { t.plan(2) const jsonData = { a: 1, b: '2' } const dispatch = function (_req, res) { res.writeHead(200, { 'Content-Type': 'application/json' }) res.end(JSON.stringify(jsonData)) } inject(dispatch, { method: 'GET', path: 'http://example.com:8080/hello' }, (err, res) => { t.assert.ifError(err) const { json } = res t.assert.deepStrictEqual(json(), jsonData) done() }) }) test('Response.json() should not throw an error if content-type is not application/json', (t, done) => { t.plan(2) const jsonData = { a: 1, b: '2' } const dispatch = function (_req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(JSON.stringify(jsonData)) } inject(dispatch, { method: 'GET', path: 'http://example.com:8080/hello' }, (err, res) => { t.assert.ifError(err) const { json } = res t.assert.deepStrictEqual(json(), jsonData) done() }) }) test('Response.json() should throw an error if the payload is not of valid JSON format', (t, done) => { t.plan(2) const dispatch = function (_req, res) { res.writeHead(200, { 'Content-Type': 'application/json' }) res.end('notAJSON') } inject(dispatch, { method: 'GET', path: 'http://example.com:8080/hello' }, (err, res) => { t.assert.ifError(err) t.assert.throws(res.json, Error) done() }) }) test('Response.stream() should provide a Readable stream', (t, done) => { const lines = [ JSON.stringify({ foo: 'bar' }), JSON.stringify({ hello: 'world' }) ] t.plan(2 + lines.length) const dispatch = function (_req, res) { res.writeHead(200, { 'Content-Type': 'multiple/json' }) for (const line of lines) { res.write(line) } res.end() } inject(dispatch, { method: 'GET', path: 'http://example.com:8080/hello' }, (err, res) => { t.assert.ifError(err) const readable = res.stream() const payload = [] t.assert.strictEqual(readable instanceof Readable, true) readable.on('data', function (chunk) { payload.push(chunk) }) readable.on('end', function () { for (let i = 0; i < lines.length; i++) { t.assert.strictEqual(lines[i], payload[i].toString()) } done() }) }) }) test('promise api should auto start (fire and forget)', (t, done) => { t.plan(1) function dispatch (_req, res) { t.assert.ok('dispatch called') res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end() } inject(dispatch, 'http://example.com:8080/hello') process.nextTick(done) }) test('disabling autostart', (t, done) => { t.plan(3) let called = false function dispatch (_req, res) { t.assert.ok('dispatch called') called = true res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end() done() } const p = inject(dispatch, { url: 'http://example.com:8080/hello', autoStart: false }) setImmediate(() => { t.assert.strictEqual(called, false) p.then(() => { t.assert.strictEqual(called, true) }) }) }) function getTestStream (encoding) { const word = 'hi' let i = 0 const stream = new Readable({ read () { this.push(word[i] ? word[i++] : null) } }) if (encoding) { stream.setEncoding(encoding) } return stream } function readStream (stream, callback) { const chunks = [] stream.on('data', (chunk) => chunks.push(chunk)) stream.on('end', () => { return callback(Buffer.concat(chunks)) }) } test('send cookie', (t, done) => { t.plan(3) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.headers.host + '|' + req.headers.cookie) } inject(dispatch, { url: 'http://example.com:8080/hello', cookies: { foo: 'bar', grass: 'àìùòlé' } }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'example.com:8080|foo=bar; grass=%C3%A0%C3%AC%C3%B9%C3%B2l%C3%A9') t.assert.strictEqual(res.rawPayload.toString(), 'example.com:8080|foo=bar; grass=%C3%A0%C3%AC%C3%B9%C3%B2l%C3%A9') done() }) }) test('send cookie with header already set', (t, done) => { t.plan(3) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.headers.host + '|' + req.headers.cookie) } inject(dispatch, { url: 'http://example.com:8080/hello', headers: { cookie: 'custom=one' }, cookies: { foo: 'bar', grass: 'àìùòlé' } }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'example.com:8080|custom=one; foo=bar; grass=%C3%A0%C3%AC%C3%B9%C3%B2l%C3%A9') t.assert.strictEqual(res.rawPayload.toString(), 'example.com:8080|custom=one; foo=bar; grass=%C3%A0%C3%AC%C3%B9%C3%B2l%C3%A9') done() }) }) test('read cookie', (t, done) => { t.plan(3) const dispatch = function (req, res) { res.setHeader('Set-Cookie', [ 'type=ninja', 'dev=me; Expires=Fri, 17 Jan 2020 20:26:08 -0000; Max-Age=1234; Domain=.home.com; Path=/wow; Secure; HttpOnly; SameSite=Strict' ]) res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.headers.host + '|' + req.headers.cookie) } inject(dispatch, { url: 'http://example.com:8080/hello', cookies: { foo: 'bar' } }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'example.com:8080|foo=bar') t.assert.deepStrictEqual(res.cookies, [ { name: 'type', value: 'ninja' }, { name: 'dev', value: 'me', expires: new Date('Fri, 17 Jan 2020 20:26:08 -0000'), maxAge: 1234, domain: '.home.com', path: '/wow', secure: true, httpOnly: true, sameSite: 'Strict' } ]) done() }) }) test('correctly handles no string headers', (t, done) => { t.plan(3) const dispatch = function (req, res) { const payload = JSON.stringify(req.headers) res.writeHead(200, { 'Content-Type': 'application/json', integer: 12, float: 3.14, null: null, string: 'string', object: { foo: 'bar' }, array: [1, 'two', 3], date, true: true, false: false }) res.end(payload) } const date = new Date(0) const headers = { integer: 12, float: 3.14, null: null, string: 'string', object: { foo: 'bar' }, array: [1, 'two', 3], date, true: true, false: false } inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello', headers }, (err, res) => { t.assert.ifError(err) t.assert.deepStrictEqual(res.headers, { integer: '12', float: '3.14', null: 'null', string: 'string', object: '[object Object]', array: ['1', 'two', '3'], date: date.toString(), true: 'true', false: 'false', connection: 'keep-alive', 'transfer-encoding': 'chunked', 'content-type': 'application/json' }) t.assert.deepStrictEqual(JSON.parse(res.payload), { integer: '12', float: '3.14', null: 'null', string: 'string', object: '[object Object]', array: '1,two,3', date: date.toString(), true: 'true', false: 'false', host: 'example.com:8080', 'user-agent': 'lightMyRequest' }) done() }) }) test('errors for invalid undefined header value', (t, done) => { t.plan(1) try { inject(() => {}, { url: '/', headers: { 'header-key': undefined } }, () => {}) } catch (err) { t.assert.ok(err) done() } }) test('example with form-auto-content', (t, done) => { t.plan(4) const dispatch = function (req, res) { let body = '' req.on('data', d => { body += d }) req.on('end', () => { res.end(body) }) } const form = formAutoContent({ myField: 'my value', myFile: fs.createReadStream('./LICENSE') }) inject(dispatch, { method: 'POST', url: 'http://example.com:8080/hello', payload: form.payload, headers: form.headers }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.statusCode, 200) t.assert.ok(/--.+\r\nContent-Disposition: form-data; name="myField"\r\n\r\nmy value\r\n--.*/.test(res.payload)) t.assert.ok(/--.+\r\nContent-Disposition: form-data; name="myFile"; filename="LICENSE"\r\n.*/.test(res.payload)) done() }) }) test('simulate invalid alter _lightMyRequest.isDone with end', (t, done) => { const dispatch = function (req) { req.resume() req._lightMyRequest.isDone = true req.on('end', () => { t.assert.ok('should have end event') done() }) } inject(dispatch, { method: 'GET', url: '/', simulate: { end: true } }, () => { t.assert.fail('should not have reply') }) }) test('simulate invalid alter _lightMyRequest.isDone without end', (t, done) => { const dispatch = function (req) { req.resume() req._lightMyRequest.isDone = true req.on('end', () => { t.assert.fail('should not have end event') }) done() } inject(dispatch, { method: 'GET', url: '/', simulate: { end: false } }, () => { t.assert.fail('should not have reply') }) }) test('no error for response destroy', (t, done) => { t.plan(2) const dispatch = function (_req, res) { res.destroy() } inject(dispatch, { method: 'GET', url: '/' }, (err, res) => { t.assert.equal(res, null) t.assert.equal(err.code, 'LIGHT_ECONNRESET') done() }) }) test('request destory without.assert.ifError', (t, done) => { t.plan(2) const dispatch = function (req) { req.destroy() } inject(dispatch, { method: 'GET', url: '/' }, (err, res) => { t.assert.equal(err.code, 'LIGHT_ECONNRESET') t.assert.equal(res, null) done() }) }) test('request destory with error', (t, done) => { t.plan(2) const fakeError = new Error('some-err') const dispatch = function (req) { req.destroy(fakeError) } inject(dispatch, { method: 'GET', url: '/' }, (err, res) => { t.assert.strictEqual(err, fakeError) t.assert.strictEqual(res, null) done() }) }) test('compatible with stream.finished', (t, done) => { t.plan(3) const dispatch = function (req, res) { finished(res, (err) => { t.assert.ok(err instanceof Error) }) req.destroy() } inject(dispatch, { method: 'GET', url: '/' }, (err, res) => { t.assert.equal(err.code, 'LIGHT_ECONNRESET') t.assert.equal(res, null) done() }) }) test('compatible with eos', (t, done) => { t.plan(4) const dispatch = function (req, res) { eos(res, (err) => { t.assert.ok(err instanceof Error) }) req.destroy() } inject(dispatch, { method: 'GET', url: '/' }, (err, res) => { t.assert.ok(err) t.assert.equal(err.code, 'LIGHT_ECONNRESET') t.assert.equal(res, null) done() }) }) test('compatible with stream.finished pipe a Stream', (t, done) => { t.plan(3) const dispatch = function (_req, res) { finished(res, (err) => { t.assert.ifError(err) }) new Readable({ read () { this.push('hello world') this.push(null) } }).pipe(res) } inject(dispatch, { method: 'GET', url: '/' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.body, 'hello world') done() }) }) test('compatible with eos, passes error correctly', (t, done) => { t.plan(3) const fakeError = new Error('some-error') const dispatch = function (req, res) { eos(res, (err) => { t.assert.strictEqual(err, fakeError) }) req.destroy(fakeError) } inject(dispatch, { method: 'GET', url: '/' }, (err, res) => { t.assert.strictEqual(err, fakeError) t.assert.strictEqual(res, null) done() }) }) test('multiple calls to req.destroy should not be called', (t, done) => { t.plan(2) const dispatch = function (req) { req.destroy() req.destroy() // twice } inject(dispatch, { method: 'GET', url: '/' }, (err, res) => { t.assert.equal(res, null) t.assert.equal(err.code, 'LIGHT_ECONNRESET') done() }) }) test('passes headers when using an express app', (t, done) => { t.plan(2) const app = express() app.get('/hello', (_req, res) => { res.setHeader('Some-Fancy-Header', 'a very cool value') res.end() }) inject(app, { method: 'GET', url: 'http://example.com:8080/hello' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.headers['some-fancy-header'], 'a very cool value') done() }) }) test('value of request url when using inject should not differ', (t, done) => { t.plan(1) const server = http.createServer() const dispatch = function (req, res) { res.end(req.url) } inject(dispatch, { method: 'GET', url: 'http://example.com:8080//hello', server }) .then(res => { t.assert.strictEqual(res.body, '//hello') }) .catch(err => t.assert.ifError(err)) .finally(done) }) test('Can parse paths with single leading slash', (t) => { t.plan(1) const parsedURL = parseURL('/test', undefined) t.assert.strictEqual(parsedURL.href, 'http://localhost/test') }) test('Can parse paths with two leading slashes', (t) => { t.plan(1) const parsedURL = parseURL('//test', undefined) t.assert.strictEqual(parsedURL.href, 'http://localhost//test') }) test('Can parse URLs with two leading slashes', (t) => { t.plan(1) const parsedURL = parseURL('https://example.com//test', undefined) t.assert.strictEqual(parsedURL.href, 'https://example.com//test') }) test('Can parse URLs with single leading slash', (t) => { t.plan(1) const parsedURL = parseURL('https://example.com/test', undefined) t.assert.strictEqual(parsedURL.href, 'https://example.com/test') }) test('Can abort a request using AbortController/AbortSignal', (t) => { t.plan(1) const dispatch = function () {} const controller = new AbortController() const promise = inject(dispatch, { method: 'GET', url: 'http://example.com:8080/hello', signal: controller.signal }) controller.abort() const wanted = new Error('The operation was aborted') wanted.name = 'AbortError' t.assert.rejects(promise, wanted) }, { skip: globalThis.AbortController == null }) test('should pass req to ServerResponse', (t, done) => { if (parseInt(process.versions.node.split('.', 1)[0], 10) < 16) { t.assert.ok('Skip because Node version < 16') t.end() return } t.plan(5) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end(req.headers.host + '|' + req.url) } inject(dispatch, 'http://example.com:8080/hello', (err, res) => { t.assert.ifError(err) t.assert.ok(res.raw.req === res.raw.res.req) t.assert.ok(res.raw.res.req.removeListener) t.assert.strictEqual(res.payload, 'example.com:8080|/hello') t.assert.strictEqual(res.rawPayload.toString(), 'example.com:8080|/hello') done() }) }) test('should work with pipeline', (t, done) => { t.plan(3) const dispatch = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }) pipeline(req.headers.host + '|' + req.url, res, () => res.end()) } inject(dispatch, 'http://example.com:8080/hello', (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.payload, 'example.com:8080|/hello') t.assert.strictEqual(res.rawPayload.toString(), 'example.com:8080|/hello') done() }) }) test('should leave the headers user-agent and content-type undefined when the headers are explicitly set to undefined in the inject', (t, done) => { t.plan(5) const dispatch = function (req, res) { t.assert.ok(Array.isArray(req.rawHeaders)) t.assert.strictEqual(req.headers['user-agent'], undefined) t.assert.strictEqual(req.headers['content-type'], undefined) t.assert.strictEqual(req.headers['x-foo'], 'bar') res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end('Ok') } inject(dispatch, { url: 'http://example.com:8080/hello', method: 'POST', headers: { 'x-foo': 'bar', 'user-agent': undefined, 'content-type': undefined }, body: {} }, (err) => { t.assert.ifError(err) done() }) }) test("passes payload when using express' send", (t, done) => { t.plan(3) const app = express() app.get('/hello', (_req, res) => { res.send('some text') }) inject(app, { method: 'GET', url: 'http://example.com:8080/hello' }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.headers['content-length'], '9') t.assert.strictEqual(res.payload, 'some text') done() }) }) test('request that is destroyed errors', (t, done) => { t.plan(2) const dispatch = function (req, res) { readStream(req, () => { req.destroy() // this should be a no-op setImmediate(() => { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end('hi') }) }) } const payload = getTestStream() inject(dispatch, { method: 'POST', url: '/', payload }, (err, res) => { t.assert.equal(res, null) t.assert.equal(err.code, 'LIGHT_ECONNRESET') done() }) }) function runFormDataUnitTest (name, { FormData, Blob }) { test(`${name} - form-data should be handled correctly`, (t, done) => { t.plan(23) const dispatch = function (req, res) { let body = '' t.assert.ok(/multipart\/form-data; boundary=----formdata-[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}(--)?$/.test(req.headers['content-type']), 'proper Content-Type provided') req.on('data', d => { body += d }) req.on('end', () => { res.end(body) }) } const form = new FormData() form.append('field', 'value') form.append('blob', new Blob(['value']), '') form.append('blob-with-type', new Blob(['value'], { type: 'text/plain' }), '') form.append('blob-with-name', new Blob(['value']), 'file.txt') form.append('number', 1) inject(dispatch, { method: 'POST', url: 'http://example.com:8080/hello', payload: form }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.statusCode, 200) const regexp = [ // header /^------formdata-[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}(--)?$/, // content-disposition /^Content-Disposition: form-data; name="(.*)"(; filename="(.*)")?$/, // content-type /^Content-Type: (.*)$/ ] const readable = Readable.from(res.body.split('\r\n')) let i = 1 readable.on('data', function (chunk) { switch (i) { case 1: case 5: case 10: case 15: case 20: { // header t.assert.ok(regexp[0].test(chunk), 'correct header') break } case 2: case 6: case 11: case 16: { // content-disposition t.assert.ok(regexp[1].test(chunk), 'correct content-disposition') break } case 7: case 12: case 17: { // content-type t.assert.ok(regexp[2].test(chunk), 'correct content-type') break } case 3: case 8: case 13: case 18: { // empty t.assert.strictEqual(chunk, '', 'correct space') break } case 4: case 9: case 14: case 19: { // value t.assert.strictEqual(chunk, 'value', 'correct value') break } } i++ }) done() }) }, { skip: FormData == null || Blob == null }) } // supports >= node@18 runFormDataUnitTest('native', { FormData: globalThis.FormData, Blob: globalThis.Blob }) // supports >= node@16 runFormDataUnitTest('undici', { FormData: require('undici').FormData, Blob: require('node:buffer').Blob }) // supports >= node@14 runFormDataUnitTest('formdata-node', { FormData: require('formdata-node').FormData, Blob: require('formdata-node').Blob }) test('QUERY method works', (t, done) => { t.plan(3) const dispatch = function (req, res) { res.writeHead(200, { 'content-type': req.headers['content-type'] }) req.pipe(res) } inject(dispatch, { method: 'QUERY', url: '/test', payload: { a: 1 } }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.headers['content-type'], 'application/json') t.assert.strictEqual(res.payload, '{"a":1}') done() }) }) test('query method works', (t, done) => { t.plan(3) const dispatch = function (req, res) { res.writeHead(200, { 'content-type': req.headers['content-type'] }) req.pipe(res) } inject(dispatch, { method: 'query', url: '/test', payload: { a: 1 } }, (err, res) => { t.assert.ifError(err) t.assert.strictEqual(res.headers['content-type'], 'application/json') t.assert.strictEqual(res.payload, '{"a":1}') done() }) }) test('should return the file content', async (t) => { const multerMiddleware = multer({ storage: multer.memoryStorage(), limits: { fileSize: 1024 } }) const app = express() app.use((req, res, next) => { if (req.headers['content-type'].indexOf('multipart/form-data') === 0) { req.multipart = true } next() }) app.post('/hello', multerMiddleware.single('textFile'), (req, res) => { res.send(req.file.buffer.toString('utf8')) }) app.use((err, req, res, next) => { console.warn(err) res.status(500).send('Something was wrong') }) const formData = new FormData() formData.append('textFile', new Blob(['some data']), 'sample.txt') const response = await inject(app, { method: 'POST', url: 'http://example.com:8080/hello', payload: formData }) t.assert.equal(response.statusCode, 200) t.assert.equal(response.payload, 'some data') })

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/krtw00/search-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server