package-rebuilds/pkgs/node-undici/node-undici-5.28.4+dfsg1+~cs23.12.11/test/client-request.js
Fabian Grünbichler afee63929c add node-undici for libgit2 1.8.1
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-09-10 11:23:24 +02:00

998 lines
24 KiB
JavaScript

/* globals AbortController */
'use strict'
const { test } = require('tap')
const { Client, errors } = require('..')
const { createServer } = require('http')
const EE = require('events')
const { kConnect } = require('../lib/core/symbols')
const { Readable } = require('stream')
const net = require('net')
const { promisify } = require('util')
const { NotSupportedError } = require('../lib/core/errors')
const { nodeMajor } = require('../lib/core/util')
const { parseFormDataString } = require('./utils/formdata')
test('request dump', (t) => {
t.plan(3)
const server = createServer((req, res) => {
res.end('hello')
})
t.teardown(server.close.bind(server))
server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
let dumped = false
client.on('disconnect', () => {
t.equal(dumped, true)
})
client.request({
path: '/',
method: 'GET'
}, (err, { body }) => {
t.error(err)
body.dump().then(() => {
dumped = true
t.pass()
})
})
})
})
test('request dump with abort signal', (t) => {
t.plan(2)
const server = createServer((req, res) => {
res.write('hello')
})
t.teardown(server.close.bind(server))
server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
client.request({
path: '/',
method: 'GET'
}, (err, { body }) => {
t.error(err)
let ac
if (!global.AbortController) {
const { AbortController } = require('abort-controller')
ac = new AbortController()
} else {
ac = new AbortController()
}
body.dump({ signal: ac.signal }).catch((err) => {
t.equal(err.name, 'AbortError')
server.close()
})
ac.abort()
})
})
})
test('request hwm', (t) => {
t.plan(2)
const server = createServer((req, res) => {
res.write('hello')
})
t.teardown(server.close.bind(server))
server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
client.request({
path: '/',
method: 'GET',
highWaterMark: 1000
}, (err, { body }) => {
t.error(err)
t.same(body.readableHighWaterMark, 1000)
body.dump()
})
})
})
test('request abort before headers', (t) => {
t.plan(6)
const signal = new EE()
const server = createServer((req, res) => {
res.end('hello')
signal.emit('abort')
})
t.teardown(server.close.bind(server))
server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
client[kConnect](() => {
client.request({
path: '/',
method: 'GET',
signal
}, (err) => {
t.type(err, errors.RequestAbortedError)
t.equal(signal.listenerCount('abort'), 0)
})
t.equal(signal.listenerCount('abort'), 1)
client.request({
path: '/',
method: 'GET',
signal
}, (err) => {
t.type(err, errors.RequestAbortedError)
t.equal(signal.listenerCount('abort'), 0)
})
t.equal(signal.listenerCount('abort'), 2)
})
})
})
test('request body destroyed on invalid callback', (t) => {
t.plan(1)
const server = createServer((req, res) => {
})
t.teardown(server.close.bind(server))
server.listen(0, () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
const body = new Readable({
read () {}
})
try {
client.request({
path: '/',
method: 'GET',
body
}, null)
} catch (err) {
t.equal(body.destroyed, true)
}
})
})
test('trailers', (t) => {
t.plan(1)
const server = createServer((req, res) => {
res.writeHead(200, { Trailer: 'Content-MD5' })
res.addTrailers({ 'Content-MD5': 'test' })
res.end()
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.close.bind(client))
const { body, trailers } = await client.request({
path: '/',
method: 'GET'
})
body
.on('data', () => t.fail())
.on('end', () => {
t.strictSame(trailers, { 'content-md5': 'test' })
})
})
})
test('destroy socket abruptly', { skip: true }, async (t) => {
t.plan(2)
const server = net.createServer((socket) => {
const lines = [
'HTTP/1.1 200 OK',
'Date: Sat, 09 Oct 2010 14:28:02 GMT',
'Connection: close',
'',
'the body'
]
socket.end(lines.join('\r\n'))
// Unfortunately calling destroy synchronously might get us flaky results,
// therefore we delay it to the next event loop run.
setImmediate(socket.destroy.bind(socket))
})
t.teardown(server.close.bind(server))
await promisify(server.listen.bind(server))(0)
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.close.bind(client))
const { statusCode, body } = await client.request({
path: '/',
method: 'GET'
})
t.equal(statusCode, 200)
body.setEncoding('utf8')
let actual = ''
for await (const chunk of body) {
actual += chunk
}
t.equal(actual, 'the body')
})
test('destroy socket abruptly with keep-alive', { skip: true }, async (t) => {
t.plan(2)
const server = net.createServer((socket) => {
const lines = [
'HTTP/1.1 200 OK',
'Date: Sat, 09 Oct 2010 14:28:02 GMT',
'Connection: keep-alive',
'Content-Length: 42',
'',
'the body'
]
socket.end(lines.join('\r\n'))
// Unfortunately calling destroy synchronously might get us flaky results,
// therefore we delay it to the next event loop run.
setImmediate(socket.destroy.bind(socket))
})
t.teardown(server.close.bind(server))
await promisify(server.listen.bind(server))(0)
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.close.bind(client))
const { statusCode, body } = await client.request({
path: '/',
method: 'GET'
})
t.equal(statusCode, 200)
body.setEncoding('utf8')
try {
/* eslint-disable */
for await (const _ of body) {
// empty on purpose
}
/* eslint-enable */
t.fail('no error')
} catch (err) {
t.pass('error happened')
}
})
test('request json', (t) => {
t.plan(1)
const obj = { asd: true }
const server = createServer((req, res) => {
res.end(JSON.stringify(obj))
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
const { body } = await client.request({
path: '/',
method: 'GET'
})
t.strictSame(obj, await body.json())
})
})
test('request long multibyte json', (t) => {
t.plan(1)
const obj = { asd: 'あ'.repeat(100000) }
const server = createServer((req, res) => {
res.end(JSON.stringify(obj))
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
const { body } = await client.request({
path: '/',
method: 'GET'
})
t.strictSame(obj, await body.json())
})
})
test('request text', (t) => {
t.plan(1)
const obj = { asd: true }
const server = createServer((req, res) => {
res.end(JSON.stringify(obj))
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
const { body } = await client.request({
path: '/',
method: 'GET'
})
t.strictSame(JSON.stringify(obj), await body.text())
})
})
test('empty host header', (t) => {
t.plan(3)
const server = createServer((req, res) => {
res.end(req.headers.host)
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const serverAddress = `localhost:${server.address().port}`
const client = new Client(`http://${serverAddress}`)
t.teardown(client.destroy.bind(client))
const getWithHost = async (host, wanted) => {
const { body } = await client.request({
path: '/',
method: 'GET',
headers: { host }
})
t.strictSame(await body.text(), wanted)
}
await getWithHost('test', 'test')
await getWithHost(undefined, serverAddress)
await getWithHost('', '')
})
})
test('request long multibyte text', (t) => {
t.plan(1)
const obj = { asd: 'あ'.repeat(100000) }
const server = createServer((req, res) => {
res.end(JSON.stringify(obj))
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
const { body } = await client.request({
path: '/',
method: 'GET'
})
t.strictSame(JSON.stringify(obj), await body.text())
})
})
test('request blob', { skip: nodeMajor < 16 }, (t) => {
t.plan(2)
const obj = { asd: true }
const server = createServer((req, res) => {
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify(obj))
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
const { body } = await client.request({
path: '/',
method: 'GET'
})
const blob = await body.blob()
t.strictSame(obj, JSON.parse(await blob.text()))
t.equal(blob.type, 'application/json')
})
})
test('request arrayBuffer', (t) => {
t.plan(2)
const obj = { asd: true }
const server = createServer((req, res) => {
res.end(JSON.stringify(obj))
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
const { body } = await client.request({
path: '/',
method: 'GET'
})
const ab = await body.arrayBuffer()
t.strictSame(Buffer.from(JSON.stringify(obj)), Buffer.from(ab))
t.ok(ab instanceof ArrayBuffer)
})
})
test('request body', { skip: nodeMajor < 16 }, (t) => {
t.plan(1)
const obj = { asd: true }
const server = createServer((req, res) => {
res.end(JSON.stringify(obj))
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
const { body } = await client.request({
path: '/',
method: 'GET'
})
let x = ''
for await (const chunk of body.body) {
x += Buffer.from(chunk)
}
t.strictSame(JSON.stringify(obj), x)
})
})
test('request post body no missing data', { skip: nodeMajor < 16 }, (t) => {
t.plan(2)
const server = createServer(async (req, res) => {
let ret = ''
for await (const chunk of req) {
ret += chunk
}
t.equal(ret, 'asd')
res.end()
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
const { body } = await client.request({
path: '/',
method: 'GET',
body: new Readable({
read () {
this.push('asd')
this.push(null)
}
}),
maxRedirections: 2
})
await body.text()
t.pass()
})
})
test('request post body no extra data handler', { skip: nodeMajor < 16 }, (t) => {
t.plan(3)
const server = createServer(async (req, res) => {
let ret = ''
for await (const chunk of req) {
ret += chunk
}
t.equal(ret, 'asd')
res.end()
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
const reqBody = new Readable({
read () {
this.push('asd')
this.push(null)
}
})
process.nextTick(() => {
t.equal(reqBody.listenerCount('data'), 0)
})
const { body } = await client.request({
path: '/',
method: 'GET',
body: reqBody,
maxRedirections: 0
})
await body.text()
t.pass()
})
})
test('request with onInfo callback', (t) => {
t.plan(3)
const infos = []
const server = createServer((req, res) => {
res.writeProcessing()
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ foo: 'bar' }))
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
await client.request({
path: '/',
method: 'GET',
onInfo: (x) => { infos.push(x) }
})
t.equal(infos.length, 1)
t.equal(infos[0].statusCode, 102)
t.pass()
})
})
test('request with onInfo callback but socket is destroyed before end of response', (t) => {
t.plan(5)
const infos = []
let response
const server = createServer((req, res) => {
response = res
res.writeProcessing()
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
try {
await client.request({
path: '/',
method: 'GET',
onInfo: (x) => {
infos.push(x)
response.destroy()
}
})
t.error()
} catch (e) {
t.ok(e)
t.equal(e.message, 'other side closed')
}
t.equal(infos.length, 1)
t.equal(infos[0].statusCode, 102)
t.pass()
})
})
test('request onInfo callback headers parsing', async (t) => {
t.plan(4)
const infos = []
const server = net.createServer((socket) => {
const lines = [
'HTTP/1.1 103 Early Hints',
'Link: </style.css>; rel=preload; as=style',
'',
'HTTP/1.1 200 OK',
'Date: Sat, 09 Oct 2010 14:28:02 GMT',
'Connection: close',
'',
'the body'
]
socket.end(lines.join('\r\n'))
})
t.teardown(server.close.bind(server))
await promisify(server.listen.bind(server))(0)
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.close.bind(client))
const { body } = await client.request({
path: '/',
method: 'GET',
onInfo: (x) => { infos.push(x) }
})
await body.dump()
t.equal(infos.length, 1)
t.equal(infos[0].statusCode, 103)
t.same(infos[0].headers, { link: '</style.css>; rel=preload; as=style' })
t.pass()
})
test('request raw responseHeaders', async (t) => {
t.plan(4)
const infos = []
const server = net.createServer((socket) => {
const lines = [
'HTTP/1.1 103 Early Hints',
'Link: </style.css>; rel=preload; as=style',
'',
'HTTP/1.1 200 OK',
'Date: Sat, 09 Oct 2010 14:28:02 GMT',
'Connection: close',
'',
'the body'
]
socket.end(lines.join('\r\n'))
})
t.teardown(server.close.bind(server))
await promisify(server.listen.bind(server))(0)
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.close.bind(client))
const { body, headers } = await client.request({
path: '/',
method: 'GET',
responseHeaders: 'raw',
onInfo: (x) => { infos.push(x) }
})
await body.dump()
t.equal(infos.length, 1)
t.same(infos[0].headers, ['Link', '</style.css>; rel=preload; as=style'])
t.same(headers, ['Date', 'Sat, 09 Oct 2010 14:28:02 GMT', 'Connection', 'close'])
t.pass()
})
test('request formData', { skip: nodeMajor < 16 }, (t) => {
t.plan(1)
const obj = { asd: true }
const server = createServer((req, res) => {
res.end(JSON.stringify(obj))
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
const { body } = await client.request({
path: '/',
method: 'GET'
})
try {
await body.formData()
t.fail('should throw NotSupportedError')
} catch (error) {
t.ok(error instanceof NotSupportedError)
}
})
})
test('request text2', (t) => {
t.plan(2)
const obj = { asd: true }
const server = createServer((req, res) => {
res.end(JSON.stringify(obj))
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
const { body } = await client.request({
path: '/',
method: 'GET'
})
const p = body.text()
let ret = ''
body.on('data', chunk => {
ret += chunk
}).on('end', () => {
t.equal(JSON.stringify(obj), ret)
})
t.strictSame(JSON.stringify(obj), await p)
})
})
test('request with FormData body', { skip: nodeMajor < 16 }, (t) => {
const { FormData } = require('../')
const { Blob } = require('buffer')
const fd = new FormData()
fd.set('key', 'value')
fd.set('file', new Blob(['Hello, world!']), 'hello_world.txt')
const server = createServer(async (req, res) => {
const contentType = req.headers['content-type']
// ensure we received a multipart/form-data header
t.ok(/^multipart\/form-data; boundary=-+formdata-undici-0\d+$/.test(contentType))
const chunks = []
for await (const chunk of req) {
chunks.push(chunk)
}
const { fileMap, fields } = await parseFormDataString(
Buffer.concat(chunks),
contentType
)
t.same(fields[0], { key: 'key', value: 'value' })
t.ok(fileMap.has('file'))
t.equal(fileMap.get('file').data.toString(), 'Hello, world!')
t.same(fileMap.get('file').info, {
filename: 'hello_world.txt',
encoding: '7bit',
mimeType: 'application/octet-stream'
})
return res.end()
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
await client.request({
path: '/',
method: 'POST',
body: fd
})
t.end()
})
})
test('request with FormData body on node < 16', { skip: nodeMajor >= 16 }, async (t) => {
t.plan(1)
// a FormData polyfill, for example
class FormData {}
const fd = new FormData()
const client = new Client('http://localhost:3000')
t.teardown(client.destroy.bind(client))
await t.rejects(client.request({
path: '/',
method: 'POST',
body: fd
}), errors.InvalidArgumentError)
})
test('request post body Buffer from string', (t) => {
t.plan(2)
const requestBody = Buffer.from('abcdefghijklmnopqrstuvwxyz')
const server = createServer(async (req, res) => {
let ret = ''
for await (const chunk of req) {
ret += chunk
}
t.equal(ret, 'abcdefghijklmnopqrstuvwxyz')
res.end()
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
const { body } = await client.request({
path: '/',
method: 'POST',
body: requestBody,
maxRedirections: 2
})
await body.text()
t.pass()
})
})
test('request post body Buffer from buffer', (t) => {
t.plan(2)
const fullBuffer = new TextEncoder().encode('abcdefghijklmnopqrstuvwxyz')
const requestBody = Buffer.from(fullBuffer.buffer, 8, 16)
const server = createServer(async (req, res) => {
let ret = ''
for await (const chunk of req) {
ret += chunk
}
t.equal(ret, 'ijklmnopqrstuvwx')
res.end()
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
const { body } = await client.request({
path: '/',
method: 'POST',
body: requestBody,
maxRedirections: 2
})
await body.text()
t.pass()
})
})
test('request post body Uint8Array', (t) => {
t.plan(2)
const fullBuffer = new TextEncoder().encode('abcdefghijklmnopqrstuvwxyz')
const requestBody = new Uint8Array(fullBuffer.buffer, 8, 16)
const server = createServer(async (req, res) => {
let ret = ''
for await (const chunk of req) {
ret += chunk
}
t.equal(ret, 'ijklmnopqrstuvwx')
res.end()
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
const { body } = await client.request({
path: '/',
method: 'POST',
body: requestBody,
maxRedirections: 2
})
await body.text()
t.pass()
})
})
test('request post body Uint32Array', (t) => {
t.plan(2)
const fullBuffer = new TextEncoder().encode('abcdefghijklmnopqrstuvwxyz')
const requestBody = new Uint32Array(fullBuffer.buffer, 8, 4)
const server = createServer(async (req, res) => {
let ret = ''
for await (const chunk of req) {
ret += chunk
}
t.equal(ret, 'ijklmnopqrstuvwx')
res.end()
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
const { body } = await client.request({
path: '/',
method: 'POST',
body: requestBody,
maxRedirections: 2
})
await body.text()
t.pass()
})
})
test('request post body Float64Array', (t) => {
t.plan(2)
const fullBuffer = new TextEncoder().encode('abcdefghijklmnopqrstuvwxyz')
const requestBody = new Float64Array(fullBuffer.buffer, 8, 2)
const server = createServer(async (req, res) => {
let ret = ''
for await (const chunk of req) {
ret += chunk
}
t.equal(ret, 'ijklmnopqrstuvwx')
res.end()
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
const { body } = await client.request({
path: '/',
method: 'POST',
body: requestBody,
maxRedirections: 2
})
await body.text()
t.pass()
})
})
test('request post body BigUint64Array', (t) => {
t.plan(2)
const fullBuffer = new TextEncoder().encode('abcdefghijklmnopqrstuvwxyz')
const requestBody = new BigUint64Array(fullBuffer.buffer, 8, 2)
const server = createServer(async (req, res) => {
let ret = ''
for await (const chunk of req) {
ret += chunk
}
t.equal(ret, 'ijklmnopqrstuvwx')
res.end()
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
const { body } = await client.request({
path: '/',
method: 'POST',
body: requestBody,
maxRedirections: 2
})
await body.text()
t.pass()
})
})
test('request post body DataView', (t) => {
t.plan(2)
const fullBuffer = new TextEncoder().encode('abcdefghijklmnopqrstuvwxyz')
const requestBody = new DataView(fullBuffer.buffer, 8, 16)
const server = createServer(async (req, res) => {
let ret = ''
for await (const chunk of req) {
ret += chunk
}
t.equal(ret, 'ijklmnopqrstuvwx')
res.end()
})
t.teardown(server.close.bind(server))
server.listen(0, async () => {
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.destroy.bind(client))
const { body } = await client.request({
path: '/',
method: 'POST',
body: requestBody,
maxRedirections: 2
})
await body.text()
t.pass()
})
})