'use strict'
const { describe, test, after } = require('node:test')
const assert = require('node:assert/strict')
const { once } = require('node:events')
const fs = require('node:fs')
const http = require('node:http')
const https = require('node:https')
const path = require('node:path')
const { PassThrough } = require('node:stream')
const { kRunning } = require('../../lib/core/symbols')
const {
Agent,
errors,
request,
stream,
pipeline,
Pool,
setGlobalDispatcher,
getGlobalDispatcher
} = require('../..')
const { tspl } = require('@matteo.collina/tspl')
const { closeServerAsPromise } = require('../utils/node-http')
describe('setGlobalDispatcher', () => {
after(() => {
// reset globalAgent to a fresh Agent instance for later tests
setGlobalDispatcher(new Agent())
})
test('fails if agent does not implement `get` method', t => {
const p = tspl(t, { plan: 1 })
p.throws(() => setGlobalDispatcher({ dispatch: 'not a function' }), errors.InvalidArgumentError)
})
test('sets global agent', async t => {
const p = tspl(t, { plan: 2 })
p.doesNotThrow(() => setGlobalDispatcher(new Agent()))
p.doesNotThrow(() => setGlobalDispatcher({ dispatch: () => {} }))
})
})
test('Agent', t => {
const p = tspl(t, { plan: 1 })
p.doesNotThrow(() => new Agent())
})
test('Agent enforces maxOrigins', async (t) => {
const p = tspl(t, { plan: 1 })
const dispatcher = new Agent({
maxOrigins: 1,
keepAliveMaxTimeout: 100,
keepAliveTimeout: 100
})
t.after(() => dispatcher.close())
const handler = (_req, res) => {
setTimeout(() => res.end('ok'), 50)
}
const server1 = http.createServer({ joinDuplicateHeaders: true }, handler)
server1.listen(0)
await once(server1, 'listening')
t.after(closeServerAsPromise(server1))
const server2 = http.createServer({ joinDuplicateHeaders: true }, handler)
server2.listen(0)
await once(server2, 'listening')
t.after(closeServerAsPromise(server2))
try {
await Promise.all([
request(`http://localhost:${server1.address().port}`, { dispatcher }),
request(`http://localhost:${server2.address().port}`, { dispatcher })
])
} catch (err) {
p.ok(err instanceof errors.MaxOriginsReachedError)
}
await p.completed
})
test('agent should call callback after closing internal pools', async (t) => {
const p = tspl(t, { plan: 2 })
const wanted = 'payload'
const server = http.createServer({ joinDuplicateHeaders: true }, (req, res) => {
res.setHeader('Content-Type', 'text/plain')
res.end(wanted)
})
t.after(closeServerAsPromise(server))
server.listen(0, () => {
const dispatcher = new Agent()
const origin = `http://localhost:${server.address().port}`
request(origin, { dispatcher })
.then(() => {
// first request should resolve
p.ok(1)
})
.catch(err => {
p.fail(err)
})
dispatcher.once('connect', () => {
dispatcher.close(() => {
request(origin, { dispatcher })
.then(() => {
p.fail('second request should not resolve')
})
.catch(err => {
p.ok(err instanceof errors.ClientDestroyedError)
})
})
})
})
await p.completed
})
test('agent close throws when callback is not a function', t => {
const p = tspl(t, { plan: 1 })
const dispatcher = new Agent()
try {
dispatcher.close({})
} catch (err) {
p.ok(err instanceof errors.InvalidArgumentError)
}
})
test('agent should close internal pools', async (t) => {
const p = tspl(t, { plan: 2 })
const wanted = 'payload'
const server = http.createServer({ joinDuplicateHeaders: true }, (req, res) => {
res.setHeader('Content-Type', 'text/plain')
res.end(wanted)
})
t.after(closeServerAsPromise(server))
server.listen(0, () => {
const dispatcher = new Agent()
const origin = `http://localhost:${server.address().port}`
request(origin, { dispatcher })
.then(() => {
// first request should resolve
p.ok(1)
})
.catch(err => {
p.fail(err)
})
dispatcher.once('connect', () => {
dispatcher.close()
.then(() => request(origin, { dispatcher }))
.then(() => {
p.fail('second request should not resolve')
})
.catch(err => {
p.ok(err instanceof errors.ClientDestroyedError)
})
})
})
await p.completed
})
test('agent should destroy internal pools and call callback', async (t) => {
const p = tspl(t, { plan: 2 })
const wanted = 'payload'
const server = http.createServer({ joinDuplicateHeaders: true }, (req, res) => {
res.setHeader('Content-Type', 'text/plain')
res.end(wanted)
})
t.after(closeServerAsPromise(server))
server.listen(0, () => {
const dispatcher = new Agent()
const origin = `http://localhost:${server.address().port}`
request(origin, { dispatcher })
.then(() => {
p.fail()
})
.catch(err => {
p.ok(err instanceof errors.ClientDestroyedError)
})
dispatcher.once('connect', () => {
dispatcher.destroy(() => {
request(origin, { dispatcher })
.then(() => {
p.fail()
})
.catch(err => {
p.ok(err instanceof errors.ClientDestroyedError)
})
})
})
})
await p.completed
})
test('agent destroy throws when callback is not a function', t => {
const p = tspl(t, { plan: 1 })
const dispatcher = new Agent()
try {
dispatcher.destroy(new Error('mock error'), {})
} catch (err) {
p.ok(err instanceof errors.InvalidArgumentError)
}
})
test('agent close/destroy callback with error', t => {
const p = tspl(t, { plan: 4 })
const dispatcher = new Agent()
p.strictEqual(dispatcher.closed, false)
dispatcher.close()
p.strictEqual(dispatcher.closed, true)
p.strictEqual(dispatcher.destroyed, false)
dispatcher.destroy(new Error('mock error'))
p.strictEqual(dispatcher.destroyed, true)
})
test('agent should destroy internal pools', async t => {
const p = tspl(t, { plan: 2 })
const wanted = 'payload'
const server = http.createServer({ joinDuplicateHeaders: true }, (req, res) => {
res.setHeader('Content-Type', 'text/plain')
res.end(wanted)
})
t.after(closeServerAsPromise(server))
server.listen(0, () => {
const dispatcher = new Agent()
const origin = `http://localhost:${server.address().port}`
request(origin, { dispatcher })
.then(() => {
p.fail()
})
.catch(err => {
p.ok(err instanceof errors.ClientDestroyedError)
})
dispatcher.once('connect', () => {
dispatcher.destroy()
.then(() => request(origin, { dispatcher }))
.then(() => {
p.fail()
})
.catch(err => {
p.ok(err instanceof errors.ClientDestroyedError)
})
})
})
await p.completed
})
test('multiple connections', async t => {
const connections = 3
const p = tspl(t, { plan: 6 * connections })
const server = http.createServer({ joinDuplicateHeaders: true }, (req, res) => {
res.writeHead(200, {
Connection: 'keep-alive',
'Keep-Alive': 'timeout=1s'
})
res.end('ok')
})
t.after(closeServerAsPromise(server))
server.listen(0, async () => {
const origin = `http://localhost:${server.address().port}`
const dispatcher = new Agent({ connections })
t.after(() => { dispatcher.close.bind(dispatcher)() })
dispatcher.on('connect', (origin, [dispatcher]) => {
p.ok(dispatcher)
})
dispatcher.on('disconnect', (origin, [dispatcher], error) => {
p.ok(dispatcher)
p.ok(error instanceof errors.InformationalError)
p.strictEqual(error.code, 'UND_ERR_INFO')
p.strictEqual(error.message, 'reset')
})
for (let i = 0; i < connections; i++) {
try {
await request(origin, { dispatcher })
p.ok(1)
} catch (err) {
p.fail(err)
}
}
})
await p.completed
})
test('agent factory supports URL parameter', async (t) => {
const p = tspl(t, { plan: 2 })
const noopHandler = {
onRequestStart () {},
onResponseStart () {},
onResponseData () {},
onResponseEnd () {
server.close()
},
onResponseError (_controller, err) {
throw err
}
}
const dispatcher = new Agent({
factory: (origin, opts) => {
p.ok(origin instanceof URL)
return new Pool(origin, opts)
}
})
const server = http.createServer({ joinDuplicateHeaders: true }, (req, res) => {
res.setHeader('Content-Type', 'text/plain')
res.end('asd')
})
server.listen(0, () => {
p.doesNotThrow(() => dispatcher.dispatch({
origin: new URL(`http://localhost:${server.address().port}`),
path: '/',
method: 'GET'
}, noopHandler))
})
await p.completed
})
test('agent factory supports string parameter', async (t) => {
const p = tspl(t, { plan: 2 })
const noopHandler = {
onRequestStart () {},
onResponseStart () {},
onResponseData () {},
onResponseEnd () {
server.close()
},
onResponseError (_controller, err) {
throw err
}
}
const dispatcher = new Agent({
factory: (origin, opts) => {
p.ok(typeof origin === 'string')
return new Pool(origin, opts)
}
})
const server = http.createServer({ joinDuplicateHeaders: true }, (req, res) => {
res.setHeader('Content-Type', 'text/plain')
res.end('asd')
})
server.listen(0, () => {
p.doesNotThrow(() => dispatcher.dispatch({
origin: `http://localhost:${server.address().port}`,
path: '/',
method: 'GET'
}, noopHandler))
})
await p.completed
})
test('with globalAgent', async t => {
const p = tspl(t, { plan: 6 })
const wanted = 'payload'
const server = http.createServer({ joinDuplicateHeaders: true }, (req, res) => {
p.strictEqual('/', req.url)
p.strictEqual('GET', req.method)
p.strictEqual(`localhost:${server.address().port}`, req.headers.host)
res.setHeader('Content-Type', 'text/plain')
res.end(wanted)
})
t.after(closeServerAsPromise(server))
server.listen(0, () => {
request(`http://localhost:${server.address().port}`)
.then(({ statusCode, headers, body }) => {
p.strictEqual(statusCode, 200)
p.strictEqual(headers['content-type'], 'text/plain')
const bufs = []
body.on('data', (buf) => {
bufs.push(buf)
})
body.on('end', () => {
p.strictEqual(wanted, Buffer.concat(bufs).toString('utf8'))
})
})
.catch(err => {
p.fail(err)
})
})
await p.completed
})
test('with local agent', async t => {
const p = tspl(t, { plan: 6 })
const wanted = 'payload'
const server = http.createServer({ joinDuplicateHeaders: true }, (req, res) => {
p.strictEqual('/', req.url)
p.strictEqual('GET', req.method)
p.strictEqual(`localhost:${server.address().port}`, req.headers.host)
res.setHeader('Content-Type', 'text/plain')
res.end(wanted)
})
t.after(closeServerAsPromise(server))
const dispatcher = new Agent({
connect: {
servername: 'agent1'
}
})
server.listen(0, () => {
request(`http://localhost:${server.address().port}`, { dispatcher })
.then(({ statusCode, headers, body }) => {
p.strictEqual(statusCode, 200)
p.strictEqual(headers['content-type'], 'text/plain')
const bufs = []
body.on('data', (buf) => {
bufs.push(buf)
})
body.on('end', () => {
p.strictEqual(wanted, Buffer.concat(bufs).toString('utf8'))
})
})
.catch(err => {
p.fail(err)
})
})
await p.completed
})
test('fails with invalid args', t => {
assert.throws(() => request(), errors.InvalidArgumentError, 'throws on missing url argument')
assert.throws(() => request(''), errors.InvalidArgumentError, 'throws on invalid url')
assert.throws(() => request({}), errors.InvalidArgumentError, 'throws on missing url.origin argument')
assert.throws(() => request({ origin: '' }), errors.InvalidArgumentError, 'throws on invalid url.origin argument')
assert.throws(() => request('https://example.com', { path: 0 }), errors.InvalidArgumentError, 'throws on opts.path argument')
assert.throws(() => request('https://example.com', { agent: new Agent() }), errors.InvalidArgumentError, 'throws on opts.path argument')
assert.throws(() => request('https://example.com', 'asd'), errors.InvalidArgumentError, 'throws on non object opts argument')
})
test('with globalAgent', async t => {
const p = tspl(t, { plan: 6 })
const wanted = 'payload'
const server = http.createServer({ joinDuplicateHeaders: true }, (req, res) => {
p.strictEqual('/', req.url)
p.strictEqual('GET', req.method)
p.strictEqual(`localhost:${server.address().port}`, req.headers.host)
res.setHeader('Content-Type', 'text/plain')
res.end(wanted)
})
t.after(closeServerAsPromise(server))
server.listen(0, () => {
stream(
`http://localhost:${server.address().port}`,
{
opaque: new PassThrough()
},
({ statusCode, headers, opaque: pt }) => {
p.strictEqual(statusCode, 200)
p.strictEqual(headers['content-type'], 'text/plain')
const bufs = []
pt.on('data', (buf) => {
bufs.push(buf)
})
pt.on('end', () => {
p.strictEqual(wanted, Buffer.concat(bufs).toString('utf8'))
})
pt.on('error', () => {
p.fail()
})
return pt
}
)
})
await p.completed
})
test('with a local agent', async t => {
const p = tspl(t, { plan: 6 })
const wanted = 'payload'
const server = http.createServer({ joinDuplicateHeaders: true }, (req, res) => {
p.strictEqual('/', req.url)
p.strictEqual('GET', req.method)
p.strictEqual(`localhost:${server.address().port}`, req.headers.host)
res.setHeader('Content-Type', 'text/plain')
res.end(wanted)
})
t.after(closeServerAsPromise(server))
const dispatcher = new Agent()
dispatcher.on('connect', (origin, [dispatcher]) => {
p.ok(dispatcher)
p.strictEqual(dispatcher[kRunning], 0)
process.nextTick(() => {
p.strictEqual(dispatcher[kRunning], 1)
})
})
server.listen(0, () => {
stream(
`http://localhost:${server.address().port}`,
{
dispatcher,
opaque: new PassThrough()
},
({ statusCode, headers, opaque: pt }) => {
p.strictEqual(statusCode, 200)
p.strictEqual(headers['content-type'], 'text/plain')
const bufs = []
pt.on('data', (buf) => {
bufs.push(buf)
})
pt.on('end', () => {
p.strictEqual(wanted, Buffer.concat(bufs).toString('utf8'))
})
pt.on('error', (err) => {
p.fail(err)
})
return pt
}
)
})
await p.completed
})
test('stream: fails with invalid URL', t => {
const p = tspl(t, { plan: 4 })
p.throws(() => stream(), errors.InvalidArgumentError, 'throws on missing url argument')
p.throws(() => stream(''), errors.InvalidArgumentError, 'throws on invalid url')
p.throws(() => stream({}), errors.InvalidArgumentError, 'throws on missing url.origin argument')
p.throws(() => stream({ origin: '' }), errors.InvalidArgumentError, 'throws on invalid url.origin argument')
})
test('with globalAgent', async t => {
const p = tspl(t, { plan: 6 })
const wanted = 'payload'
const server = http.createServer({ joinDuplicateHeaders: true }, (req, res) => {
p.strictEqual('/', req.url)
p.strictEqual('GET', req.method)
p.strictEqual(`localhost:${server.address().port}`, req.headers.host)
res.setHeader('Content-Type', 'text/plain')
res.end(wanted)
})
t.after(closeServerAsPromise(server))
server.listen(0, () => {
const bufs = []
pipeline(
`http://localhost:${server.address().port}`,
{},
({ statusCode, headers, body }) => {
p.strictEqual(statusCode, 200)
p.strictEqual(headers['content-type'], 'text/plain')
return body
}
)
.end()
.on('data', buf => {
bufs.push(buf)
})
.on('end', () => {
p.strictEqual(wanted, Buffer.concat(bufs).toString('utf8'))
})
.on('error', (err) => {
p.fail(err)
})
})
await p.completed
})
test('with a local agent', async t => {
const p = tspl(t, { plan: 6 })
const wanted = 'payload'
const server = http.createServer({ joinDuplicateHeaders: true }, (req, res) => {
p.strictEqual('/', req.url)
p.strictEqual('GET', req.method)
p.strictEqual(`localhost:${server.address().port}`, req.headers.host)
res.setHeader('Content-Type', 'text/plain')
res.end(wanted)
})
t.after(closeServerAsPromise(server))
const dispatcher = new Agent()
server.listen(0, () => {
const bufs = []
pipeline(
`http://localhost:${server.address().port}`,
{ dispatcher },
({ statusCode, headers, body }) => {
p.strictEqual(statusCode, 200)
p.strictEqual(headers['content-type'], 'text/plain')
return body
}
)
.end()
.on('data', buf => {
bufs.push(buf)
})
.on('end', () => {
p.strictEqual(wanted, Buffer.concat(bufs).toString('utf8'))
})
.on('error', () => {
p.fail()
})
})
await p.completed
})
test('pipeline: fails with invalid URL', t => {
const p = tspl(t, { plan: 4 })
p.throws(() => pipeline(), errors.InvalidArgumentError, 'throws on missing url argument')
p.throws(() => pipeline(''), errors.InvalidArgumentError, 'throws on invalid url')
p.throws(() => pipeline({}), errors.InvalidArgumentError, 'throws on missing url.origin argument')
p.throws(() => pipeline({ origin: '' }), errors.InvalidArgumentError, 'throws on invalid url.origin argument')
})
test('pipeline: fails with invalid onInfo', async (t) => {
const p = tspl(t, { plan: 2 })
pipeline({ origin: 'http://localhost', path: '/', onInfo: 'foo' }, () => {}).on('error', (err) => {
p.ok(err instanceof errors.InvalidArgumentError)
p.equal(err.message, 'invalid onInfo callback')
})
await p.completed
})
test('request: fails with invalid onInfo', async (t) => {
try {
await request({ origin: 'http://localhost', path: '/', onInfo: 'foo' })
assert.fail('should throw')
} catch (e) {
assert.ok(e)
assert.strictEqual(e.message, 'invalid onInfo callback')
}
})
test('stream: fails with invalid onInfo', async (t) => {
try {
await stream({ origin: 'http://localhost', path: '/', onInfo: 'foo' }, () => new PassThrough())
assert.fail('should throw')
} catch (e) {
assert.ok(e)
assert.strictEqual(e.message, 'invalid onInfo callback')
}
})
test('constructor validations', t => {
const p = tspl(t, { plan: 3 })
p.throws(() => new Agent({ factory: 'ASD' }), errors.InvalidArgumentError, 'throws on invalid opts argument')
p.throws(() => new Agent({ maxOrigins: -1 }), errors.InvalidArgumentError, 'maxOrigins must be a number greater than 0')
p.throws(() => new Agent({ maxOrigins: 'foo' }), errors.InvalidArgumentError, 'maxOrigins must be a number greater than 0')
})
test('dispatch validations', async t => {
const dispatcher = new Agent()
const noopHandler = {
onRequestStart () {},
onResponseStart () {},
onResponseData () {},
onResponseEnd () {
server.close()
},
onResponseError (_controller, err) {
throw err
}
}
const server = http.createServer({ joinDuplicateHeaders: true }, (req, res) => {
res.setHeader('Content-Type', 'text/plain')
res.end('asd')
})
const p = tspl(t, { plan: 6 })
p.throws(() => dispatcher.dispatch('ASD'), errors.InvalidArgumentError, 'throws on missing handler')
p.throws(() => dispatcher.dispatch('ASD', noopHandler), errors.InvalidArgumentError, 'throws on invalid opts argument type')
p.throws(() => dispatcher.dispatch({}, noopHandler), errors.InvalidArgumentError, 'throws on invalid opts.origin argument')
p.throws(() => dispatcher.dispatch({ origin: '' }, noopHandler), errors.InvalidArgumentError, 'throws on invalid opts.origin argument')
p.throws(() => dispatcher.dispatch({}, {}), errors.InvalidArgumentError, 'throws on invalid handler.onResponseError')
server.listen(0, () => {
p.doesNotThrow(() => dispatcher.dispatch({
origin: new URL(`http://localhost:${server.address().port}`),
path: '/',
method: 'GET'
}, noopHandler))
})
await p.completed
})
test('drain', async t => {
const p = tspl(t, { plan: 2 })
const dispatcher = new Agent({
connections: 1,
pipelining: 1
})
dispatcher.on('drain', () => {
p.ok(1)
})
class Handler {
onRequestStart () {}
onResponseStart () {}
onResponseData () {}
onResponseEnd () {}
onResponseError () {
p.fail()
}
}
const server = http.createServer({ joinDuplicateHeaders: true }, (req, res) => {
res.setHeader('Content-Type', 'text/plain')
res.end('asd')
})
t.after(closeServerAsPromise(server))
server.listen(0, () => {
p.strictEqual(dispatcher.dispatch({
origin: `http://localhost:${server.address().port}`,
method: 'GET',
path: '/'
}, new Handler()), false)
})
await p.completed
})
test('global api', async t => {
const p = tspl(t, { plan: 6 * 2 })
const server = http.createServer({ joinDuplicateHeaders: true }, (req, res) => {
if (req.url === '/bar') {
p.strictEqual(req.method, 'PUT')
p.strictEqual(req.url, '/bar')
} else {
p.strictEqual(req.method, 'GET')
p.strictEqual(req.url, '/foo')
}
req.pipe(res)
})
t.after(closeServerAsPromise(server))
server.listen(0, async () => {
const origin = `http://localhost:${server.address().port}`
await request(origin, { path: '/foo' })
await request(`${origin}/foo`)
await request({ origin, path: '/foo' })
await stream({ origin, path: '/foo' }, () => new PassThrough())
await request({ protocol: 'http:', hostname: 'localhost', port: server.address().port, path: '/foo' })
await request(`${origin}/bar`, { body: 'asd' })
})
await p.completed
})
test('global api throws', t => {
const origin = 'http://asd'
assert.throws(() => request(`${origin}/foo`, { path: '/foo' }), errors.InvalidArgumentError)
assert.throws(() => request({ origin, path: 0 }, { path: '/foo' }), errors.InvalidArgumentError)
assert.throws(() => request({ origin, pathname: 0 }, { path: '/foo' }), errors.InvalidArgumentError)
assert.throws(() => request({ origin: 0 }, { path: '/foo' }), errors.InvalidArgumentError)
assert.throws(() => request(0), errors.InvalidArgumentError)
assert.throws(() => request(1), errors.InvalidArgumentError)
})
test('unreachable request rejects and can be caught', async t => {
const p = tspl(t, { plan: 1 })
request('https://thisis.not/avalid/url').catch(() => {
p.ok(1)
})
await p.completed
})
test('connect is not valid', t => {
const p = tspl(t, { plan: 1 })
p.throws(() => new Agent({ connect: false }), errors.InvalidArgumentError, 'connect must be a function or an object')
})
test('the dispatcher is truly global', t => {
const agent = getGlobalDispatcher()
assert.ok(require.resolve('../../index.js') in require.cache)
delete require.cache[require.resolve('../../index.js')]
assert.strictEqual(require.resolve('../../index.js') in require.cache, false)
const undiciFresh = require('../../index.js')
assert.ok(require.resolve('../../index.js') in require.cache)
assert.strictEqual(agent, undiciFresh.getGlobalDispatcher())
})
test('stats', async t => {
const p = tspl(t, { plan: 7 })
const wanted = 'payload'
const server = http.createServer({ joinDuplicateHeaders: true }, (req, res) => {
p.strictEqual('/', req.url)
p.strictEqual('GET', req.method)
res.end(wanted)
})
t.after(closeServerAsPromise(server))
const dispatcher = new Agent({
connect: {
servername: 'agent1'
}
})
server.listen(0, () => {
request(`http://localhost:${server.address().port}`, { dispatcher })
.then(({ statusCode, headers, body }) => {
p.strictEqual(statusCode, 200)
const originForStats = `http://localhost:${server.address().port}`
const agentStats = dispatcher.stats[originForStats]
p.strictEqual(agentStats.connected, 1)
p.strictEqual(agentStats.pending, 0)
p.strictEqual(agentStats.running, 0)
p.strictEqual(agentStats.size, 0)
})
.catch(err => {
p.fail(err)
})
})
await p.completed
})
test('forwards TLS certificate validation callbacks via tls option', async (t) => {
const server = https.createServer({
key: fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'key.pem')),
cert: fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'cert.pem'))
}, (req, res) => {
res.end('ok')
})
t.after(() => server.close())
server.listen(0, '127.0.0.1')
await once(server, 'listening')
const expectedError = new Error('rejected by custom checkServerIdentity')
let identityChecks = 0
const agent = new Agent({
tls: {
ca: fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'ca.pem'), 'utf8'),
servername: 'agent1',
checkServerIdentity () {
identityChecks++
return expectedError
}
}
})
t.after(() => agent.close())
await assert.rejects(
request(`https://localhost:${server.address().port}`, { dispatcher: agent }),
/rejected by custom checkServerIdentity/
)
assert.strictEqual(identityChecks, 1)
})
test('forwards TLS certificate validation callbacks via connect option', async (t) => {
const server = https.createServer({
key: fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'key.pem')),
cert: fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'cert.pem'))
}, (req, res) => {
res.end('ok')
})
t.after(() => server.close())
server.listen(0, '127.0.0.1')
await once(server, 'listening')
const expectedError = new Error('rejected by custom checkServerIdentity')
let identityChecks = 0
const agent = new Agent({
connect: {
ca: fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'ca.pem'), 'utf8'),
servername: 'agent1',
checkServerIdentity () {
identityChecks++
return expectedError
}
}
})
t.after(() => agent.close())
await assert.rejects(
request(`https://localhost:${server.address().port}`, { dispatcher: agent }),
/rejected by custom checkServerIdentity/
)
assert.strictEqual(identityChecks, 1)
})
test('copies connect and tls options before storing them', async (t) => {
const server = http.createServer({ joinDuplicateHeaders: true }, (req, res) => {
res.end('ok')
})
t.after(closeServerAsPromise(server))
server.listen(0, '127.0.0.1')
await once(server, 'listening')
const connect = { timeout: 100 }
const tls = { servername: 'agent1' }
let capturedOptions
const agent = new Agent({
connect,
tls,
factory (origin, options) {
capturedOptions = options
return new Pool(origin, options)
}
})
t.after(() => agent.close())
connect.timeout = 200
tls.servername = 'agent2'
await request(`http://localhost:${server.address().port}`, { dispatcher: agent })
assert.notStrictEqual(capturedOptions.connect, connect)
assert.notStrictEqual(capturedOptions.tls, tls)
assert.deepStrictEqual(capturedOptions.connect, { timeout: 100 })
assert.deepStrictEqual(capturedOptions.tls, { servername: 'agent1' })
})