diff --git a/test/node-test/diagnostics-channel/connect-error.js b/test/node-test/diagnostics-channel/connect-error.js new file mode 100644 index 00000000000..35559fab86a --- /dev/null +++ b/test/node-test/diagnostics-channel/connect-error.js @@ -0,0 +1,65 @@ +'use strict' + +const { test, skip } = require('node:test') +const { tspl } = require('@matteo.collina/tspl') + +let diagnosticsChannel + +try { + diagnosticsChannel = require('diagnostics_channel') +} catch { + skip('missing diagnostics_channel') + process.exit(0) +} + +const { Client } = require('../../..') + +test('Diagnostics channel - connect error', (t) => { + const connectError = new Error('custom error') + const assert = tspl(t, { plan: 16 }) + + let _connector + diagnosticsChannel.channel('undici:client:beforeConnect').subscribe(({ connectParams, connector }) => { + _connector = connector + + assert.equal(typeof _connector, 'function') + assert.equal(Object.keys(connectParams).length, 6) + + const { host, hostname, protocol, port, servername } = connectParams + + assert.equal(host, 'localhost:1234') + assert.equal(hostname, 'localhost') + assert.equal(port, '1234') + assert.equal(protocol, 'http:') + assert.equal(servername, null) + }) + + diagnosticsChannel.channel('undici:client:connectError').subscribe(({ error, connectParams, connector }) => { + assert.equal(Object.keys(connectParams).length, 6) + assert.equal(_connector, connector) + + const { host, hostname, protocol, port, servername } = connectParams + + assert.equal(error, connectError) + assert.equal(host, 'localhost:1234') + assert.equal(hostname, 'localhost') + assert.equal(port, '1234') + assert.equal(protocol, 'http:') + assert.equal(servername, null) + }) + + const client = new Client('http://localhost:1234', { + connect: (_, cb) => { cb(connectError, null) } + }) + + return new Promise((resolve) => { + client.request({ + path: '/', + method: 'GET' + }, (err, data) => { + assert.equal(err, connectError) + client.close() + resolve() + }) + }) +}) diff --git a/test/node-test/diagnostics-channel/error.js b/test/node-test/diagnostics-channel/error.js new file mode 100644 index 00000000000..2a737d70b33 --- /dev/null +++ b/test/node-test/diagnostics-channel/error.js @@ -0,0 +1,57 @@ +'use strict' + +const { test, skip, after } = require('node:test') +const { tspl } = require('@matteo.collina/tspl') + +let diagnosticsChannel + +try { + diagnosticsChannel = require('diagnostics_channel') +} catch { + skip('missing diagnostics_channel') + process.exit(0) +} + +const { Client } = require('../..') +const { createServer } = require('http') + +test('Diagnostics channel - error', (t) => { + const assert = tspl(t, { plan: 3 }) + const server = createServer((req, res) => { + res.destroy() + }) + after(server.close.bind(server)) + + const reqHeaders = { + foo: undefined, + bar: 'bar' + } + + let _req + diagnosticsChannel.channel('undici:request:create').subscribe(({ request }) => { + _req = request + }) + + diagnosticsChannel.channel('undici:request:error').subscribe(({ request, error }) => { + assert.equal(_req, request) + assert.equal(error.code, 'UND_ERR_SOCKET') + }) + + return new Promise((resolve) => { + server.listen(0, () => { + const client = new Client(`http://localhost:${server.address().port}`, { + keepAliveTimeout: 300e3 + }) + + client.request({ + path: '/', + method: 'GET', + headers: reqHeaders + }, (err, data) => { + assert.equal(err.code, 'UND_ERR_SOCKET') + client.close() + resolve() + }) + }) + }) +}) diff --git a/test/node-test/diagnostics-channel/get.js b/test/node-test/diagnostics-channel/get.js new file mode 100644 index 00000000000..e0feeb2fe56 --- /dev/null +++ b/test/node-test/diagnostics-channel/get.js @@ -0,0 +1,149 @@ +'use strict' + +const { test, skip, after } = require('node:test') +const { tspl } = require('@matteo.collina/tspl') + +let diagnosticsChannel + +try { + diagnosticsChannel = require('diagnostics_channel') +} catch { + skip('missing diagnostics_channel') + process.exit(0) +} + +const { Client } = require('../..') +const { createServer } = require('http') + +test('Diagnostics channel - get', (t) => { + const assert = tspl(t, { plan: 32 }) + const server = createServer((req, res) => { + res.setHeader('Content-Type', 'text/plain') + res.setHeader('trailer', 'foo') + res.write('hello') + res.addTrailers({ + foo: 'oof' + }) + res.end() + }) + + after(server.close.bind(server)) + + const reqHeaders = { + foo: undefined, + bar: 'bar' + } + + let _req + diagnosticsChannel.channel('undici:request:create').subscribe(({ request }) => { + _req = request + assert.equal(request.origin, `http://localhost:${server.address().port}`) + assert.equal(request.completed, false) + assert.equal(request.method, 'GET') + assert.equal(request.path, '/') + assert.equal(request.headers, 'bar: bar\r\n') + request.addHeader('hello', 'world') + assert.equal(request.headers, 'bar: bar\r\nhello: world\r\n') + }) + + let _connector + diagnosticsChannel.channel('undici:client:beforeConnect').subscribe(({ connectParams, connector }) => { + _connector = connector + + assert.equal(typeof _connector, 'function') + assert.equal(Object.keys(connectParams).length, 6) + + const { host, hostname, protocol, port, servername } = connectParams + + assert.equal(host, `localhost:${server.address().port}`) + assert.equal(hostname, 'localhost') + assert.equal(port, String(server.address().port)) + assert.equal(protocol, 'http:') + assert.equal(servername, null) + }) + + let _socket + diagnosticsChannel.channel('undici:client:connected').subscribe(({ connectParams, socket, connector }) => { + _socket = socket + + assert.equal(_connector, connector) + assert.equal(Object.keys(connectParams).length, 6) + + const { host, hostname, protocol, port, servername } = connectParams + + assert.equal(host, `localhost:${server.address().port}`) + assert.equal(hostname, 'localhost') + assert.equal(port, String(server.address().port)) + assert.equal(protocol, 'http:') + assert.equal(servername, null) + }) + + diagnosticsChannel.channel('undici:client:sendHeaders').subscribe(({ request, headers, socket }) => { + assert.equal(_req, request) + assert.equal(_socket, socket) + + const expectedHeaders = [ + 'GET / HTTP/1.1', + `host: localhost:${server.address().port}`, + 'connection: keep-alive', + 'bar: bar', + 'hello: world' + ] + + assert.equal(headers, expectedHeaders.join('\r\n') + '\r\n') + }) + + diagnosticsChannel.channel('undici:request:headers').subscribe(({ request, response }) => { + assert.equal(_req, request) + assert.equal(response.statusCode, 200) + const expectedHeaders = [ + Buffer.from('Content-Type'), + Buffer.from('text/plain'), + Buffer.from('trailer'), + Buffer.from('foo'), + Buffer.from('Date'), + response.headers[5], // This is a date + Buffer.from('Connection'), + Buffer.from('keep-alive'), + Buffer.from('Keep-Alive'), + Buffer.from('timeout=5'), + Buffer.from('Transfer-Encoding'), + Buffer.from('chunked') + ] + assert.deepStrictEqual(response.headers, expectedHeaders) + assert.equal(response.statusText, 'OK') + }) + + let endEmitted = false + + return new Promise((resolve) => { + diagnosticsChannel.channel('undici:request:trailers').subscribe(({ request, trailers }) => { + assert.equal(request.completed, true) + assert.equal(_req, request) + // This event is emitted after the last chunk has been added to the body stream, + // not when it was consumed by the application + assert.equal(endEmitted, false) + assert.deepStrictEqual(trailers, [Buffer.from('foo'), Buffer.from('oof')]) + resolve() + }) + + server.listen(0, () => { + const client = new Client(`http://localhost:${server.address().port}`, { + keepAliveTimeout: 300e3 + }) + + client.request({ + path: '/', + method: 'GET', + headers: reqHeaders + }, (err, data) => { + assert.ok(!err) + client.close() + + data.body.on('end', function () { + endEmitted = true + }) + }) + }) + }) +}) diff --git a/test/node-test/diagnostics-channel/post-stream.js b/test/node-test/diagnostics-channel/post-stream.js new file mode 100644 index 00000000000..3510e74993b --- /dev/null +++ b/test/node-test/diagnostics-channel/post-stream.js @@ -0,0 +1,155 @@ +'use strict' + +const { test, skip, after } = require('node:test') +const { tspl } = require('@matteo.collina/tspl') +const { Readable } = require('stream') + +let diagnosticsChannel + +try { + diagnosticsChannel = require('diagnostics_channel') +} catch { + skip('missing diagnostics_channel') + process.exit(0) +} + +const { Client } = require('../..') +const { createServer } = require('http') + +test('Diagnostics channel - post stream', (t) => { + const assert = tspl(t, { plan: 33 }) + const server = createServer((req, res) => { + req.resume() + res.setHeader('Content-Type', 'text/plain') + res.setHeader('trailer', 'foo') + res.write('hello') + res.addTrailers({ + foo: 'oof' + }) + res.end() + }) + after(server.close.bind(server)) + + const reqHeaders = { + foo: undefined, + bar: 'bar' + } + const body = Readable.from(['hello', ' ', 'world']) + + let _req + diagnosticsChannel.channel('undici:request:create').subscribe(({ request }) => { + _req = request + assert.equal(request.completed, false) + assert.equal(request.method, 'POST') + assert.equal(request.path, '/') + assert.equal(request.headers, 'bar: bar\r\n') + request.addHeader('hello', 'world') + assert.equal(request.headers, 'bar: bar\r\nhello: world\r\n') + assert.deepStrictEqual(request.body, body) + }) + + let _connector + diagnosticsChannel.channel('undici:client:beforeConnect').subscribe(({ connectParams, connector }) => { + _connector = connector + + assert.equal(typeof _connector, 'function') + assert.equal(Object.keys(connectParams).length, 6) + + const { host, hostname, protocol, port, servername } = connectParams + + assert.equal(host, `localhost:${server.address().port}`) + assert.equal(hostname, 'localhost') + assert.equal(port, String(server.address().port)) + assert.equal(protocol, 'http:') + assert.equal(servername, null) + }) + + let _socket + diagnosticsChannel.channel('undici:client:connected').subscribe(({ connectParams, socket, connector }) => { + _socket = socket + + assert.equal(Object.keys(connectParams).length, 6) + assert.equal(_connector, connector) + + const { host, hostname, protocol, port, servername } = connectParams + + assert.equal(host, `localhost:${server.address().port}`) + assert.equal(hostname, 'localhost') + assert.equal(port, String(server.address().port)) + assert.equal(protocol, 'http:') + assert.equal(servername, null) + }) + + diagnosticsChannel.channel('undici:client:sendHeaders').subscribe(({ request, headers, socket }) => { + assert.equal(_req, request) + assert.equal(_socket, socket) + + const expectedHeaders = [ + 'POST / HTTP/1.1', + `host: localhost:${server.address().port}`, + 'connection: keep-alive', + 'bar: bar', + 'hello: world' + ] + + assert.equal(headers, expectedHeaders.join('\r\n') + '\r\n') + }) + + diagnosticsChannel.channel('undici:request:headers').subscribe(({ request, response }) => { + assert.equal(_req, request) + assert.equal(response.statusCode, 200) + const expectedHeaders = [ + Buffer.from('Content-Type'), + Buffer.from('text/plain'), + Buffer.from('trailer'), + Buffer.from('foo'), + Buffer.from('Date'), + response.headers[5], // This is a date + Buffer.from('Connection'), + Buffer.from('keep-alive'), + Buffer.from('Keep-Alive'), + Buffer.from('timeout=5'), + Buffer.from('Transfer-Encoding'), + Buffer.from('chunked') + ] + assert.deepStrictEqual(response.headers, expectedHeaders) + assert.equal(response.statusText, 'OK') + }) + + diagnosticsChannel.channel('undici:request:bodySent').subscribe(({ request }) => { + assert.equal(_req, request) + }) + + let endEmitted = false + + return new Promise((resolve) => { + diagnosticsChannel.channel('undici:request:trailers').subscribe(({ request, trailers }) => { + assert.equal(request.completed, true) + assert.equal(_req, request) + // This event is emitted after the last chunk has been added to the body stream, + // not when it was consumed by the application + assert.equal(endEmitted, false) + assert.deepStrictEqual(trailers, [Buffer.from('foo'), Buffer.from('oof')]) + resolve() + }) + + server.listen(0, () => { + const client = new Client(`http://localhost:${server.address().port}`, { + keepAliveTimeout: 300e3 + }) + + client.request({ + path: '/', + method: 'POST', + headers: reqHeaders, + body + }, (err, data) => { + assert.ok(!err) + client.close() + data.body.on('end', function () { + endEmitted = true + }) + }) + }) + }) +}) diff --git a/test/node-test/diagnostics-channel/post.js b/test/node-test/diagnostics-channel/post.js new file mode 100644 index 00000000000..1f0475bd556 --- /dev/null +++ b/test/node-test/diagnostics-channel/post.js @@ -0,0 +1,154 @@ +'use strict' + +const { test, skip, after } = require('node:test') +const { tspl } = require('@matteo.collina/tspl') + +let diagnosticsChannel + +try { + diagnosticsChannel = require('diagnostics_channel') +} catch { + skip('missing diagnostics_channel') + process.exit(0) +} + +const { Client } = require('../../..') +const { createServer } = require('http') + +test('Diagnostics channel - post', (t) => { + const assert = tspl(t, { plan: 33 }) + const server = createServer((req, res) => { + req.resume() + res.setHeader('Content-Type', 'text/plain') + res.setHeader('trailer', 'foo') + res.write('hello') + res.addTrailers({ + foo: 'oof' + }) + res.end() + }) + after(server.close.bind(server)) + + const reqHeaders = { + foo: undefined, + bar: 'bar' + } + + let _req + diagnosticsChannel.channel('undici:request:create').subscribe(({ request }) => { + _req = request + assert.equal(request.completed, false) + assert.equal(request.method, 'POST') + assert.equal(request.path, '/') + assert.equal(request.headers, 'bar: bar\r\n') + request.addHeader('hello', 'world') + assert.equal(request.headers, 'bar: bar\r\nhello: world\r\n') + assert.deepStrictEqual(request.body, Buffer.from('hello world')) + }) + + let _connector + diagnosticsChannel.channel('undici:client:beforeConnect').subscribe(({ connectParams, connector }) => { + _connector = connector + + assert.equal(typeof _connector, 'function') + assert.equal(Object.keys(connectParams).length, 6) + + const { host, hostname, protocol, port, servername } = connectParams + + assert.equal(host, `localhost:${server.address().port}`) + assert.equal(hostname, 'localhost') + assert.equal(port, String(server.address().port)) + assert.equal(protocol, 'http:') + assert.equal(servername, null) + }) + + let _socket + diagnosticsChannel.channel('undici:client:connected').subscribe(({ connectParams, socket, connector }) => { + _socket = socket + + assert.equal(Object.keys(connectParams).length, 6) + assert.equal(_connector, connector) + + const { host, hostname, protocol, port, servername } = connectParams + + assert.equal(host, `localhost:${server.address().port}`) + assert.equal(hostname, 'localhost') + assert.equal(port, String(server.address().port)) + assert.equal(protocol, 'http:') + assert.equal(servername, null) + }) + + diagnosticsChannel.channel('undici:client:sendHeaders').subscribe(({ request, headers, socket }) => { + assert.equal(_req, request) + assert.equal(_socket, socket) + + const expectedHeaders = [ + 'POST / HTTP/1.1', + `host: localhost:${server.address().port}`, + 'connection: keep-alive', + 'bar: bar', + 'hello: world' + ] + + assert.equal(headers, expectedHeaders.join('\r\n') + '\r\n') + }) + + diagnosticsChannel.channel('undici:request:headers').subscribe(({ request, response }) => { + assert.equal(_req, request) + assert.equal(response.statusCode, 200) + const expectedHeaders = [ + Buffer.from('Content-Type'), + Buffer.from('text/plain'), + Buffer.from('trailer'), + Buffer.from('foo'), + Buffer.from('Date'), + response.headers[5], // This is a date + Buffer.from('Connection'), + Buffer.from('keep-alive'), + Buffer.from('Keep-Alive'), + Buffer.from('timeout=5'), + Buffer.from('Transfer-Encoding'), + Buffer.from('chunked') + ] + assert.deepStrictEqual(response.headers, expectedHeaders) + assert.equal(response.statusText, 'OK') + }) + + diagnosticsChannel.channel('undici:request:bodySent').subscribe(({ request }) => { + assert.equal(_req, request) + }) + + let endEmitted = false + + return new Promise((resolve) => { + diagnosticsChannel.channel('undici:request:trailers').subscribe(({ request, trailers }) => { + assert.equal(request.completed, true) + assert.equal(_req, request) + // This event is emitted after the last chunk has been added to the body stream, + // not when it was consumed by the application + assert.equal(endEmitted, false) + assert.deepStrictEqual(trailers, [Buffer.from('foo'), Buffer.from('oof')]) + resolve() + }) + + server.listen(0, () => { + const client = new Client(`http://localhost:${server.address().port}`, { + keepAliveTimeout: 300e3 + }) + + client.request({ + path: '/', + method: 'POST', + headers: reqHeaders, + body: 'hello world' + }, (err, data) => { + assert.ok(!err) + client.close() + + data.body.on('end', function () { + endEmitted = true + }) + }) + }) + }) +})