'use strict'
const { test, describe } = require('node:test')
const { Client, Pool, errors } = require('..')
const net = require('node:net')
const assert = require('node:assert')
const skip = !!process.env.CITGM
// Using describe instead of test to avoid the timeout
describe('prioritize socket errors over timeouts', { skip }, async () => {
const client = new Pool('http://foorbar.invalid:1234', { connectTimeout: 1 })
const { promise, resolve } = Promise.withResolvers()
client.request({ method: 'GET', path: '/foobar' })
.then(() => {
assert.fail('should have errored')
resolve()
})
.catch((err) => {
assert.strictEqual(err.code, 'ENOTFOUND')
assert.strictEqual(err.code !== 'UND_ERR_CONNECT_TIMEOUT', true)
resolve()
})
// block for 1s which is enough for the dns lookup to complete and the
// Timeout to fire
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, Number(1000))
await promise
})
// mock net.connect to avoid the dns lookup
net.connect = function (options) {
return new net.Socket(options)
}
async function assertConnectTimeout (dispatcher, t) {
t.plan(3)
await new Promise((resolve, reject) => {
// Connection timeouts use FastTimers, which have a deliberately low
// resolution, and Windows adds an extra setImmediate before timing out.
const timeout = setTimeout(() => {
dispatcher.destroy()
reject(new Error('connect-timeout callback did not fire'))
}, 5e3)
dispatcher.request({
path: '/',
method: 'GET'
}, (err) => {
try {
t.assert.ok(err instanceof errors.ConnectTimeoutError)
t.assert.strictEqual(err.code, 'UND_ERR_CONNECT_TIMEOUT')
t.assert.strictEqual(err.message, 'Connect Timeout Error (attempted address: localhost:9000, timeout: 1000ms)')
resolve()
} catch (error) {
reject(error)
} finally {
clearTimeout(timeout)
}
})
})
}
test('connect-timeout (Client)', { skip }, async t => {
const client = new Client('http://localhost:9000', {
connectTimeout: 1e3
})
t.after(() => client.close().catch(() => {}))
await assertConnectTimeout(client, t)
})
test('connect-timeout (Pool)', { skip }, async t => {
const client = new Pool('http://localhost:9000', {
connectTimeout: 1e3
})
t.after(() => client.close().catch(() => {}))
await assertConnectTimeout(client, t)
})
test('autoSelectFamily AggregateError with ETIMEDOUT is normalized to ConnectTimeoutError', { skip }, async t => {
t.plan(5)
const aggregate = new AggregateError([
Object.assign(new Error('connect ETIMEDOUT 127.0.0.1:9000'), { code: 'ETIMEDOUT' }),
Object.assign(new Error('connect ETIMEDOUT ::1:9000'), { code: 'ETIMEDOUT' })
], 'connect ETIMEDOUT')
aggregate.code = 'ETIMEDOUT'
net.connect = function (options) {
const socket = new net.Socket(options)
socket.autoSelectFamilyAttemptedAddresses = ['127.0.0.1:9000', '::1:9000']
setImmediate(() => {
socket.destroy(aggregate)
})
return socket
}
const client = new Client('http://localhost:9000', {
connectTimeout: 1e3
})
t.after(() => client.close().catch(() => {}))
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('timeout waiting for callback'))
}, 5e3)
client.request({
path: '/',
method: 'GET'
}, (err) => {
try {
t.assert.ok(err instanceof errors.ConnectTimeoutError)
t.assert.strictEqual(err.code, 'UND_ERR_CONNECT_TIMEOUT')
t.assert.strictEqual(err.message, 'Connect Timeout Error (attempted addresses: 127.0.0.1:9000, ::1:9000, timeout: 1000ms)')
t.assert.ok(err.cause instanceof AggregateError)
t.assert.strictEqual(err.cause, aggregate)
resolve()
} catch (error) {
reject(error)
} finally {
clearTimeout(timeout)
}
})
})
})