diff --git a/lib/api/readable.js b/lib/api/readable.js index 25b40c109c0..f2eca3f822e 100644 --- a/lib/api/readable.js +++ b/lib/api/readable.js @@ -284,16 +284,17 @@ function chunksDecode (chunks, length) { return '' } const buffer = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks, length) + const bufferLength = buffer.length + // Skip BOM. const start = - buffer.length >= 3 && - // Skip BOM. - buffer[0] === 0xef && - buffer[1] === 0xbb && - buffer[2] === 0xbf - ? 3 - : 0 - return buffer.utf8Slice(start, buffer.length - start) + bufferLength > 2 && + buffer[0] === 0xef && + buffer[1] === 0xbb && + buffer[2] === 0xbf + ? 3 + : 0 + return buffer.utf8Slice(start, bufferLength) } function consumeEnd (consume) { diff --git a/test/readable.js b/test/readable.js new file mode 100644 index 00000000000..72327482aa2 --- /dev/null +++ b/test/readable.js @@ -0,0 +1,170 @@ +'use strict' + +const { tspl } = require('@matteo.collina/tspl') +const { test, describe } = require('node:test') +const Readable = require('../lib/api/readable') + +describe('Readable', () => { + test('avoid body reordering', async function (t) { + t = tspl(t, { plan: 1 }) + + function resume () { + } + function abort () { + } + const r = new Readable({ resume, abort }) + + r.push(Buffer.from('hello')) + + process.nextTick(() => { + r.push(Buffer.from('world')) + r.push(null) + }) + + const text = await r.text() + + t.strictEqual(text, 'helloworld') + }) + + test('destroy timing text', async function (t) { + t = tspl(t, { plan: 1 }) + + function resume () { + } + function abort () { + } + + const r = new Readable({ resume, abort }) + r.destroy(new Error('kaboom')) + + await t.rejects(r.text(), new Error('kaboom')) + }) + + test('destroy timing promise', async function (t) { + t = tspl(t, { plan: 1 }) + + function resume () { + } + function abort () { + } + const r = await new Promise(resolve => { + const r = new Readable({ resume, abort }) + r.destroy(new Error('kaboom')) + resolve(r) + }) + await new Promise(resolve => { + r.on('error', err => { + t.ok(err) + resolve(null) + }) + }) + }) + + test('.arrayBuffer()', async function (t) { + t = tspl(t, { plan: 1 }) + + function resume () { + } + function abort () { + } + const r = new Readable({ resume, abort }) + + r.push(Buffer.from('hello world')) + + process.nextTick(() => { + r.push(null) + }) + + const arrayBuffer = await r.arrayBuffer() + + const expected = new ArrayBuffer(11) + const view = new Uint8Array(expected) + view.set(Buffer.from('hello world')) + t.deepStrictEqual(arrayBuffer, expected) + }) + + test('.json()', async function (t) { + t = tspl(t, { plan: 1 }) + + function resume () { + } + function abort () { + } + const r = new Readable({ resume, abort }) + + r.push(Buffer.from('{"hello": "world"}')) + + process.nextTick(() => { + r.push(null) + }) + + const obj = await r.json() + + t.deepStrictEqual(obj, { hello: 'world' }) + }) + + test('.text()', async function (t) { + t = tspl(t, { plan: 1 }) + + function resume () { + } + function abort () { + } + const r = new Readable({ resume, abort }) + + r.push(Buffer.from('hello world')) + + process.nextTick(() => { + r.push(null) + }) + + const text = await r.text() + + t.strictEqual(text, 'hello world') + }) + + test('ignore BOM', async function (t) { + t = tspl(t, { plan: 1 }) + + function resume () { + } + function abort () { + } + const r = new Readable({ resume, abort }) + + r.push('\uFEFF') + r.push(Buffer.from('hello world')) + + process.nextTick(() => { + r.push(null) + }) + + const text = await r.text() + + t.strictEqual(text, 'hello world') + }) + + test('.bodyUsed', async function (t) { + t = tspl(t, { plan: 3 }) + + function resume () { + } + function abort () { + } + const r = new Readable({ resume, abort }) + + r.push(Buffer.from('hello world')) + + process.nextTick(() => { + r.push(null) + }) + + t.strictEqual(r.bodyUsed, false) + + const text = await r.text() + + t.strictEqual(r.bodyUsed, true) + + t.strictEqual(text, 'hello world') + }) +}) diff --git a/test/readable.test.js b/test/readable.test.js deleted file mode 100644 index 8e73301ea57..00000000000 --- a/test/readable.test.js +++ /dev/null @@ -1,60 +0,0 @@ -'use strict' - -const { tspl } = require('@matteo.collina/tspl') -const { test } = require('node:test') -const Readable = require('../lib/api/readable') - -test('avoid body reordering', async function (t) { - t = tspl(t, { plan: 1 }) - - function resume () { - } - function abort () { - } - const r = new Readable({ resume, abort }) - - r.push(Buffer.from('hello')) - - process.nextTick(() => { - r.push(Buffer.from('world')) - r.push(null) - }) - - const text = await r.text() - - t.strictEqual(text, 'helloworld') -}) - -test('destroy timing text', async function (t) { - t = tspl(t, { plan: 1 }) - - function resume () { - } - function abort () { - } - - const r = new Readable({ resume, abort }) - r.destroy(new Error('kaboom')) - - await t.rejects(r.text(), new Error('kaboom')) -}) - -test('destroy timing promise', async function (t) { - t = tspl(t, { plan: 1 }) - - function resume () { - } - function abort () { - } - const r = await new Promise(resolve => { - const r = new Readable({ resume, abort }) - r.destroy(new Error('kaboom')) - resolve(r) - }) - await new Promise(resolve => { - r.on('error', err => { - t.ok(err) - resolve(null) - }) - }) -})