|
| 1 | +/* eslint-env mocha */ |
| 2 | +import assert from 'assert'; |
| 3 | +import https from 'https'; |
| 4 | +import net from 'net'; |
| 5 | +import fs from 'fs'; |
| 6 | +import path from 'path'; |
| 7 | +import {fileURLToPath} from 'url'; |
| 8 | +import axios from '../../../index.js'; |
| 9 | + |
| 10 | +/** __dirname replacement for ESM */ |
| 11 | +const __filename = fileURLToPath(import.meta.url); |
| 12 | +const __dirname = path.dirname(__filename); |
| 13 | + |
| 14 | +/** Get a port that will refuse connections: bind to a random port and close it. */ |
| 15 | +async function getClosedPort() { |
| 16 | + return await new Promise((resolve) => { |
| 17 | + const srv = net.createServer(); |
| 18 | + srv.listen(0, '127.0.0.1', () => { |
| 19 | + const {port} = srv.address(); |
| 20 | + srv.close(() => resolve(port)); |
| 21 | + }); |
| 22 | + }); |
| 23 | +} |
| 24 | + |
| 25 | +describe('adapters – network-error details', function () { |
| 26 | + this.timeout(5000); |
| 27 | + |
| 28 | + it('should expose ECONNREFUSED and set error.cause on connection refusal', async function () { |
| 29 | + const port = await getClosedPort(); |
| 30 | + |
| 31 | + try { |
| 32 | + await axios.get(`http://127.0.0.1:${port}`, { timeout: 500 }); |
| 33 | + assert.fail('request unexpectedly succeeded'); |
| 34 | + } catch (err) { |
| 35 | + assert.ok(err instanceof Error, 'should be an Error'); |
| 36 | + assert.strictEqual(err.isAxiosError, true, 'isAxiosError should be true'); |
| 37 | + |
| 38 | + // New behavior: Node error code is surfaced and original error is linked via cause |
| 39 | + assert.strictEqual(err.code, 'ECONNREFUSED'); |
| 40 | + assert.ok('cause' in err, 'error.cause should exist'); |
| 41 | + assert.ok(err.cause instanceof Error, 'cause should be an Error'); |
| 42 | + assert.strictEqual(err.cause && err.cause.code, 'ECONNREFUSED'); |
| 43 | + |
| 44 | + // Message remains a string (content may include the code prefix) |
| 45 | + assert.strictEqual(typeof err.message, 'string'); |
| 46 | + } |
| 47 | + }); |
| 48 | + |
| 49 | + it('should expose self-signed TLS error and set error.cause', async function () { |
| 50 | + // Use the same certs already present for adapter tests in this folder |
| 51 | + const keyPath = path.join(__dirname, 'key.pem'); |
| 52 | + const certPath = path.join(__dirname, 'cert.pem'); |
| 53 | + |
| 54 | + const key = fs.readFileSync(keyPath); |
| 55 | + const cert = fs.readFileSync(certPath); |
| 56 | + |
| 57 | + const httpsServer = https.createServer({ key, cert }, (req, res) => res.end('ok')); |
| 58 | + |
| 59 | + await new Promise((resolve) => httpsServer.listen(0, '127.0.0.1', resolve)); |
| 60 | + const {port} = httpsServer.address(); |
| 61 | + |
| 62 | + try { |
| 63 | + await axios.get(`https://127.0.0.1:${port}`, { |
| 64 | + timeout: 500, |
| 65 | + httpsAgent: new https.Agent({ rejectUnauthorized: true }) // Explicit: reject self-signed |
| 66 | + }); |
| 67 | + assert.fail('request unexpectedly succeeded'); |
| 68 | + } catch (err) { |
| 69 | + const codeStr = String(err.code); |
| 70 | + // OpenSSL/Node variants: SELF_SIGNED_CERT_IN_CHAIN, DEPTH_ZERO_SELF_SIGNED_CERT, UNABLE_TO_VERIFY_LEAF_SIGNATURE |
| 71 | + assert.ok(/SELF_SIGNED|UNABLE_TO_VERIFY_LEAF_SIGNATURE|DEPTH_ZERO/.test(codeStr), 'unexpected TLS code: ' + codeStr); |
| 72 | + |
| 73 | + assert.ok('cause' in err, 'error.cause should exist'); |
| 74 | + assert.ok(err.cause instanceof Error, 'cause should be an Error'); |
| 75 | + |
| 76 | + const causeCode = String(err.cause && err.cause.code); |
| 77 | + assert.ok(/SELF_SIGNED|UNABLE_TO_VERIFY_LEAF_SIGNATURE|DEPTH_ZERO/.test(causeCode), 'unexpected cause code: ' + causeCode); |
| 78 | + |
| 79 | + assert.strictEqual(typeof err.message, 'string'); |
| 80 | + } finally { |
| 81 | + await new Promise((resolve) => httpsServer.close(resolve)); |
| 82 | + } |
| 83 | + }); |
| 84 | +}); |
0 commit comments