diff --git a/lib/body.js b/lib/body.js index c7ffa5b..5889330 100644 --- a/lib/body.js +++ b/lib/body.js @@ -10,7 +10,9 @@ const FetchError = require('./fetch-error.js') let convert try { convert = require('encoding').convert -} catch (e) {} +} catch (e) { + // defer error until textConverted is called +} const INTERNALS = Symbol('Body internals') const CONSUME_BODY = Symbol('consumeBody') @@ -69,16 +71,16 @@ class Body { )) } - json () { - return this[CONSUME_BODY]().then(buf => { - try { - return JSON.parse(buf.toString()) - } catch (er) { - return Promise.reject(new FetchError( - `invalid json response body at ${ - this.url} reason: ${er.message}`, 'invalid-json')) - } - }) + async json () { + try { + const buf = await this[CONSUME_BODY]() + return JSON.parse(buf.toString()) + } catch (er) { + throw new FetchError( + `invalid json response body at ${this.url} reason: ${er.message}`, + 'invalid-json' + ) + } } text () { diff --git a/test/fixtures/server.js b/test/fixtures/server.js index 4f50b7d..05c1b69 100644 --- a/test/fixtures/server.js +++ b/test/fixtures/server.js @@ -61,6 +61,7 @@ class TestServer { res.statusCode = 200 res.setHeader('Content-Type', 'text/plain') res.setHeader('Content-Encoding', 'gzip') + // eslint-disable-next-line promise/catch-or-return new zlib.Gzip().end('hello world').concat().then(buf => res.end(buf)) } @@ -68,6 +69,7 @@ class TestServer { res.statusCode = 200 res.setHeader('Content-Type', 'text/plain') res.setHeader('Content-Encoding', 'gzip') + // eslint-disable-next-line promise/catch-or-return new zlib.Gzip().end('hello world').concat().then(buf => res.end(buf.slice(0, buf.length - 8))) } @@ -76,6 +78,7 @@ class TestServer { res.statusCode = 200 res.setHeader('Content-Type', 'text/plain') res.setHeader('Content-Encoding', 'deflate') + // eslint-disable-next-line promise/catch-or-return new zlib.Deflate().end('hello world').concat().then(buf => res.end(buf)) } @@ -96,6 +99,7 @@ class TestServer { res.statusCode = 200 res.setHeader('Content-Type', 'text/plain') res.setHeader('Content-Encoding', 'deflate') + // eslint-disable-next-line promise/catch-or-return new zlib.DeflateRaw().end('hello world').concat().then(buf => res.end(buf)) } diff --git a/test/index.js b/test/index.js index 75f059e..6acc1c4 100644 --- a/test/index.js +++ b/test/index.js @@ -16,6 +16,7 @@ const { getTotalBytes, extractContentType } = Body const Blob = require('../lib/blob.js') const realZlib = require('zlib') const { lookup } = require('dns') +const { promisify } = require('util') const supportToString = ({ [Symbol.toStringTag]: 'z', }).toString() === '[object z]' @@ -121,70 +122,77 @@ t.test('reject with error on network failure', t => type: 'system', })) -t.test('resolve into response', t => - fetch(`${base}hello`).then(res => { - t.equal(res.headers.get('content-type'), 'text/plain') - return res.text().then(result => { - t.equal(res.bodyUsed, true) - t.equal(result, 'world') - }) - })) +t.test('resolve into response', async t => { + const res = await fetch(`${base}hello`) + t.equal(res.headers.get('content-type'), 'text/plain') + const result = await res.text() + t.equal(res.bodyUsed, true) + t.equal(result, 'world') +}) -t.test('accept html response (like plain text)', t => - fetch(`${base}html`).then(res => { - t.equal(res.headers.get('content-type'), 'text/html') - return res.text().then(result => { - t.equal(res.bodyUsed, true) - t.equal(result, '') - }) - })) +t.test('accept html response (like plain text)', async t => { + const res = await fetch(`${base}html`) + t.equal(res.headers.get('content-type'), 'text/html') + const result = await res.text() + t.equal(res.bodyUsed, true) + t.equal(result, '') +}) -t.test('accept json response', t => - fetch(`${base}json`).then(res => { - t.equal(res.headers.get('content-type'), 'application/json') - return res.json().then(result => { - t.equal(res.bodyUsed, true) - t.strictSame(result, { name: 'value' }) - }) - })) +t.test('accept json response', async t => { + const res = await fetch(`${base}json`) + t.equal(res.headers.get('content-type'), 'application/json') + const result = await res.json() + t.equal(res.bodyUsed, true) + t.strictSame(result, { name: 'value' }) +}) -t.test('send request with custom hedaers', t => - fetch(`${base}inspect`, { +t.test('send request with custom hedaers', async t => { + const res = await fetch(`${base}inspect`, { headers: { 'x-custom-header': 'abc' }, - }).then(res => res.json()).then(res => - t.equal(res.headers['x-custom-header'], 'abc'))) + }) + const json = await res.json() + t.equal(json.headers['x-custom-header'], 'abc') +}) -t.test('accept headers instance', t => - fetch(`${base}inspect`, { +t.test('accept headers instance', async t => { + const res = await fetch(`${base}inspect`, { headers: new Headers({ 'x-custom-header': 'abc' }), - }).then(res => res.json()).then(res => - t.equal(res.headers['x-custom-header'], 'abc'))) + }) + const json = await res.json() + t.equal(json.headers['x-custom-header'], 'abc') +}) -t.test('accept custom host header', t => - fetch(`${base}inspect`, { +t.test('accept custom host header', async t => { + const res = await fetch(`${base}inspect`, { headers: { host: 'example.com', }, - }).then(res => res.json()).then(res => - t.equal(res.headers.host, 'example.com'))) + }) + const json = await res.json() + t.equal(json.headers.host, 'example.com') +}) -t.test('accept custom HoSt header', t => - fetch(`${base}inspect`, { +t.test('accept custom HoSt header', async t => { + const res = await fetch(`${base}inspect`, { headers: { HoSt: 'example.com', }, - }).then(res => res.json()).then(res => - t.equal(res.headers.host, 'example.com'))) + }) + const json = await res.json() + t.equal(json.headers.host, 'example.com') +}) -t.test('follow redirects', t => { +t.test('follow redirects', async t => { const codes = [301, 302, 303, 307, 308, 'chain'] t.plan(codes.length) - codes.forEach(code => t.test(`${code}`, t => - fetch(`${base}redirect/${code}`).then(res => { + for (const code of codes) { + t.test(code, async t => { + const res = await fetch(`${base}redirect/${code}`) t.equal(res.url, `${base}inspect`) t.equal(res.status, 200) t.equal(res.ok, true) - }))) + }) + } }) t.test('redirect to different host strips headers', async (t) => { @@ -223,47 +231,45 @@ t.test('redirect to different host strips headers', async (t) => { t.ok(res.ok) }) -t.test('follow POST request redirect with GET', t => { - const codes = [301, 302] - t.plan(codes.length) - codes.forEach(code => t.test(`${code}`, t => { - const url = `${base}redirect/${code}` - const opts = { - method: 'POST', - body: 'a=1', - } - return fetch(url, opts).then(res => { +t.test('follow POST request redirect with GET', async t => { + for (const code of [301, 302]) { + t.test(code, async t => { + const url = `${base}redirect/${code}` + const opts = { + method: 'POST', + body: 'a=1', + } + const res = await fetch(url, opts) t.equal(res.url, `${base}inspect`) t.equal(res.status, 200) - return res.json().then(result => { - t.equal(result.method, 'GET') - t.equal(result.body, '') - }) + const result = await res.json() + t.equal(result.method, 'GET') + t.equal(result.body, '') }) - })) + } }) -t.test('follow PATCH request redirect with PATCH', t => { +t.test('follow PATCH request redirect with PATCH', async t => { const codes = [301, 302, 307] t.plan(codes.length) - codes.forEach(code => t.test(`${code}`, t => { - const url = `${base}redirect/${code}` - const opts = { - method: 'PATCH', - body: 'a=1', - } - return fetch(url, opts).then(res => { + for (const code of codes) { + t.test(code, async t => { + const url = `${base}redirect/${code}` + const opts = { + method: 'PATCH', + body: 'a=1', + } + const res = await fetch(url, opts) t.equal(res.url, `${base}inspect`) t.equal(res.status, 200) - return res.json().then(result => { - t.equal(result.method, 'PATCH') - t.equal(result.body, 'a=1') - }) + const result = await res.json() + t.equal(result.method, 'PATCH') + t.equal(result.body, 'a=1') }) - })) + } }) -t.test('no follow non-GET redirect if body is readable stream', t => { +t.test('no follow non-GET redirect if body is readable stream', async t => { const url = `${base}redirect/307` const body = new Minipass() body.pause() @@ -273,110 +279,105 @@ t.test('no follow non-GET redirect if body is readable stream', t => { method: 'PATCH', body, } - return t.rejects(fetch(url, opts), { + await t.rejects(fetch(url, opts), { name: 'FetchError', type: 'unsupported-redirect', }) }) -t.test('obey maximum redirect, reject case', t => { +t.test('obey maximum redirect, reject case', async t => { const url = `${base}redirect/chain` const opts = { follow: 1, } - return t.rejects(fetch(url, opts), { + await t.rejects(fetch(url, opts), { name: 'FetchError', type: 'max-redirect', }) }) -t.test('obey redirect chain, resolve case', t => { +t.test('obey redirect chain, resolve case', async t => { const url = `${base}redirect/chain` const opts = { follow: 2, } - return fetch(url, opts).then(res => { - t.equal(res.url, `${base}inspect`) - t.equal(res.status, 200) - }) + const res = await fetch(url, opts) + t.equal(res.url, `${base}inspect`) + t.equal(res.status, 200) }) -t.test('allow not following redirect', t => { +t.test('allow not following redirect', async t => { const url = `${base}redirect/301` const opts = { follow: 0, } - return t.rejects(fetch(url, opts), { + await t.rejects(fetch(url, opts), { name: 'FetchError', type: 'max-redirect', }) }) -t.test('redirect mode, manual flag', t => { +t.test('redirect mode, manual flag', async t => { const url = `${base}redirect/301` const opts = { redirect: 'manual', } - return fetch(url, opts).then(res => { - t.equal(res.url, url) - t.equal(res.status, 301) - t.equal(res.headers.get('location'), `${base}inspect`) - }) + const res = await fetch(url, opts) + t.equal(res.url, url) + t.equal(res.status, 301) + t.equal(res.headers.get('location'), `${base}inspect`) }) -t.test('redirect mode, error flag', t => { +t.test('redirect mode, error flag', async t => { const url = `${base}redirect/301` const opts = { redirect: 'error', } - return t.rejects(fetch(url, opts), { + await t.rejects(fetch(url, opts), { name: 'FetchError', type: 'no-redirect', }) }) -t.test('redirect mode, manual flag when there is no redirect', t => { +t.test('redirect mode, manual flag when there is no redirect', async t => { const url = `${base}hello` const opts = { redirect: 'manual', } - return fetch(url, opts).then(res => { - t.equal(res.url, url) - t.equal(res.status, 200) - t.equal(res.headers.get('location'), null) - }) + const res = await fetch(url, opts) + t.equal(res.url, url) + t.equal(res.status, 200) + t.equal(res.headers.get('location'), null) }) -t.test('redirect code 301 and keep existing headers', t => { +t.test('redirect code 301 and keep existing headers', async t => { const url = `${base}redirect/301` const opts = { headers: new Headers({ 'x-custom-header': 'abc' }), } - return fetch(url, opts).then(res => { - t.equal(res.url, `${base}inspect`) - return res.json() - }).then(res => t.equal(res.headers['x-custom-header'], 'abc')) + const res = await fetch(url, opts) + t.equal(res.url, `${base}inspect`) + const json = await res.json() + t.equal(json.headers['x-custom-header'], 'abc') }) -t.test('treat broken redirect as ordinary response (follow)', t => { +t.test('treat broken redirect as ordinary response (follow)', async t => { const url = `${base}redirect/no-location` - return fetch(url).then(res => { - t.equal(res.url, url) - t.equal(res.status, 301) - t.equal(res.headers.get('location'), null) - }) + const res = await fetch(url) + t.equal(res.url, url) + t.equal(res.status, 301) + t.equal(res.headers.get('location'), null) }) -t.test('treat broken redirect as ordinary response (manual)', t => { +t.test('treat broken redirect as ordinary response (manual)', async t => { const url = `${base}redirect/no-location` const opts = { redirect: 'manual', } - return fetch(url, opts).then(res => { - t.equal(res.url, url) - t.equal(res.status, 301) - t.equal(res.headers.get('location'), null) - }) + const res = await fetch(url, opts) + t.equal(res.url, url) + t.equal(res.status, 301) + t.equal(res.headers.get('location'), null) }) t.test('set redirected property on response when redirect', t => @@ -398,136 +399,142 @@ t.test('ignore invalid headers', t => { t.end() }) -t.test('handle client-error response', t => { +t.test('handle client-error response', async t => { const url = `${base}error/400` - return fetch(url).then(res => { - t.equal(res.headers.get('content-type'), 'text/plain') - t.equal(res.status, 400) - t.equal(res.statusText, 'Bad Request') - t.equal(res.ok, false) - return res.text().then(result => { - t.equal(res.bodyUsed, true) - t.equal(result, 'client error') - }) - }) + const res = await fetch(url) + t.equal(res.headers.get('content-type'), 'text/plain') + t.equal(res.status, 400) + t.equal(res.statusText, 'Bad Request') + t.equal(res.ok, false) + const result = await res.text() + t.equal(res.bodyUsed, true) + t.equal(result, 'client error') }) -t.test('handle server-error response', t => { +t.test('handle server-error response', async t => { const url = `${base}error/500` - return fetch(url).then(res => { - t.equal(res.headers.get('content-type'), 'text/plain') - t.equal(res.status, 500) - t.equal(res.statusText, 'Internal Server Error') - t.equal(res.ok, false) - return res.text().then(result => { - t.equal(res.bodyUsed, true) - t.equal(result, 'server error') - }) - }) -}) - -t.test('handle network-error response', t => - t.rejects(fetch(`${base}error/reset`), { + const res = await fetch(url) + t.equal(res.headers.get('content-type'), 'text/plain') + t.equal(res.status, 500) + t.equal(res.statusText, 'Internal Server Error') + t.equal(res.ok, false) + const result = await res.text() + t.equal(res.bodyUsed, true) + t.equal(result, 'server error') +}) + +t.test('handle network-error response', async t => { + await t.rejects(fetch(`${base}error/reset`), { name: 'FetchError', code: 'ECONNRESET', - })) + }) +}) -t.test('handle DNS-error response', t => - t.rejects(fetch('http://domain.invalid'), { +t.test('handle DNS-error response', async t => { + await t.rejects(fetch('http://domain.invalid'), { name: 'FetchError', // this error depends on the platform and dns server in use, // but it should be one of these two codes code: /^(ENOTFOUND|EAI_AGAIN)$/, - })) + }) +}) -t.test('reject invalid json response', t => - fetch(`${base}error/json`).then(res => { - t.equal(res.headers.get('content-type'), 'application/json') - return t.rejects(res.json(), { - name: 'FetchError', - type: 'invalid-json', - }) - })) +t.test('reject invalid json response', async t => { + const res = await fetch(`${base}error/json`) + t.equal(res.headers.get('content-type'), 'application/json') + await t.rejects(res.json(), { + name: 'FetchError', + type: 'invalid-json', + }) +}) -t.test('reject invalid json response', t => - fetch(`${base}error/json`).then(res => { - t.equal(res.headers.get('content-type'), 'application/json') - return t.rejects(res.json(), { - name: 'FetchError', - type: 'invalid-json', - }) - })) +t.test('reject invalid json response', async t => { + const res = await fetch(`${base}error/json`) + t.equal(res.headers.get('content-type'), 'application/json') + await t.rejects(res.json(), { + name: 'FetchError', + type: 'invalid-json', + }) +}) -t.test('handle no content response', t => - fetch(`${base}no-content`).then(res => { - t.equal(res.status, 204) - t.equal(res.statusText, 'No Content') - t.equal(res.ok, true) - return res.text().then(result => t.equal(result, '')) - })) +t.test('handle no content response', async t => { + const res = await fetch(`${base}no-content`) + t.equal(res.status, 204) + t.equal(res.statusText, 'No Content') + t.equal(res.ok, true) + const result = await res.text() + t.equal(result, '') +}) -t.test('reject parsing no content response as json', t => - fetch(`${base}no-content`).then(res => { - t.equal(res.status, 204) - t.equal(res.statusText, 'No Content') - t.equal(res.ok, true) - return t.rejects(res.json(), { - name: 'FetchError', - type: 'invalid-json', - }) - })) +t.test('reject parsing no content response as json', async t => { + const res = await fetch(`${base}no-content`) + t.equal(res.status, 204) + t.equal(res.statusText, 'No Content') + t.equal(res.ok, true) + await t.rejects(res.json(), { + name: 'FetchError', + type: 'invalid-json', + }) +}) -t.test('handle no content response with gzip encoding', t => - fetch(`${base}no-content/gzip`).then(res => { - t.equal(res.status, 204) - t.equal(res.statusText, 'No Content') - t.equal(res.headers.get('content-encoding'), 'gzip') - t.equal(res.ok, true) - return res.text().then(result => t.equal(result, '')) - })) +t.test('handle no content response with gzip encoding', async t => { + const res = await fetch(`${base}no-content/gzip`) + t.equal(res.status, 204) + t.equal(res.statusText, 'No Content') + t.equal(res.headers.get('content-encoding'), 'gzip') + t.equal(res.ok, true) + const result = await res.text() + t.equal(result, '') +}) -t.test('handle not modified response', t => - fetch(`${base}not-modified`).then(res => { - t.equal(res.status, 304) - t.equal(res.statusText, 'Not Modified') - t.equal(res.ok, false) - return res.text().then(result => t.equal(result, '')) - })) +t.test('handle not modified response', async t => { + const res = await fetch(`${base}not-modified`) + t.equal(res.status, 304) + t.equal(res.statusText, 'Not Modified') + t.equal(res.ok, false) + const result = await res.text() + t.equal(result, '') +}) -t.test('handle not modified response with gzip encoding', t => - fetch(`${base}not-modified/gzip`).then(res => { - t.equal(res.status, 304) - t.equal(res.statusText, 'Not Modified') - t.equal(res.headers.get('content-encoding'), 'gzip') - t.equal(res.ok, false) - return res.text().then(result => t.equal(result, '')) - })) +t.test('handle not modified response with gzip encoding', async t => { + const res = await fetch(`${base}not-modified/gzip`) + t.equal(res.status, 304) + t.equal(res.statusText, 'Not Modified') + t.equal(res.headers.get('content-encoding'), 'gzip') + t.equal(res.ok, false) + const result = await res.text() + t.equal(result, '') +}) -t.test('decompress gzip response', t => - fetch(`${base}gzip`).then(res => { - t.equal(res.headers.get('content-type'), 'text/plain') - return res.text().then(result => t.equal(result, 'hello world')) - })) +t.test('decompress gzip response', async t => { + const res = await fetch(`${base}gzip`) + t.equal(res.headers.get('content-type'), 'text/plain') + const result = await res.text() + t.equal(result, 'hello world') +}) -t.test('decompress slightly invalid gzip response', t => - fetch(`${base}gzip-truncated`).then(res => { - t.equal(res.headers.get('content-type'), 'text/plain') - return res.text().then(result => t.equal(result, 'hello world')) - })) +t.test('decompress slightly invalid gzip response', async t => { + const res = await fetch(`${base}gzip-truncated`) + t.equal(res.headers.get('content-type'), 'text/plain') + const result = await res.text() + t.equal(result, 'hello world') +}) -t.test('decompress deflate response', t => - fetch(`${base}deflate`).then(res => { - t.equal(res.headers.get('content-type'), 'text/plain') - return res.text().then(result => t.equal(result, 'hello world')) - })) +t.test('decompress deflate response', async t => { + const res = await fetch(`${base}deflate`) + t.equal(res.headers.get('content-type'), 'text/plain') + const result = await res.text() + t.equal(result, 'hello world') +}) -t.test('decompress deflate raw response from old apache server', t => - fetch(`${base}deflate-raw`).then(res => { - t.equal(res.headers.get('content-type'), 'text/plain') - return res.text().then(result => t.equal(result, 'hello world')) - })) +t.test('decompress deflate raw response from old apache server', async t => { + const res = await fetch(`${base}deflate-raw`) + t.equal(res.headers.get('content-type'), 'text/plain') + const result = await res.text() + t.equal(result, 'hello world') +}) -t.test('decompress brotli response', t => { +t.test('decompress brotli response', async t => { // if the node core zlib doesn't export brotli functions, we'll end up // rejecting the request with an error that comes from minizlib, assert // that here @@ -537,53 +544,55 @@ t.test('decompress brotli response', t => { }, 'rejects the promise') } - return fetch(`${base}brotli`).then(res => { - t.equal(res.headers.get('content-type'), 'text/plain') - return res.text().then(result => t.equal(result, 'hello world')) - }) + const res = await fetch(`${base}brotli`) + t.equal(res.headers.get('content-type'), 'text/plain') + const result = await res.text() + t.equal(result, 'hello world') }) -t.test('handle no content response with brotli encoding', t => - fetch(`${base}no-content/brotli`).then(res => { - t.equal(res.status, 204) - t.equal(res.statusText, 'No Content') - t.equal(res.headers.get('content-encoding'), 'br') - t.equal(res.ok, true) - return res.text().then(result => t.equal(result, '')) - })) +t.test('handle no content response with brotli encoding', async t => { + const res = await fetch(`${base}no-content/brotli`) + t.equal(res.status, 204) + t.equal(res.statusText, 'No Content') + t.equal(res.headers.get('content-encoding'), 'br') + t.equal(res.ok, true) + const result = await res.text() + t.equal(result, '') +}) -t.test('skip decompression if unsupported', t => - fetch(`${base}sdch`).then(res => { - t.equal(res.headers.get('content-type'), 'text/plain') - return res.text().then(result => t.equal(result, 'fake sdch string')) - })) +t.test('skip decompression if unsupported', async t => { + const res = await fetch(`${base}sdch`) + t.equal(res.headers.get('content-type'), 'text/plain') + const result = await res.text() + t.equal(result, 'fake sdch string') +}) -t.test('reject if response compression is invalid', t => - fetch(`${base}invalid-content-encoding`).then(res => { - t.equal(res.headers.get('content-type'), 'text/plain') - return t.rejects(res.text(), { - name: 'FetchError', - code: 'Z_DATA_ERROR', - }) - })) +t.test('reject if response compression is invalid', async t => { + const res = await fetch(`${base}invalid-content-encoding`) + t.equal(res.headers.get('content-type'), 'text/plain') + await t.rejects(res.text(), { + name: 'FetchError', + code: 'Z_DATA_ERROR', + }) +}) -t.test('handle errors on the body stream even if it is not used', t => { - fetch(`${base}invalid-content-encoding`) - .then(res => t.equal(res.status, 200)) - // Wait a few ms to see if a uncaught error occurs - .then(() => setTimeout(() => t.end(), 20)) +t.test('handle errors on the body stream even if it is not used', async t => { + const res = await fetch(`${base}invalid-content-encoding`) + t.equal(res.status, 200) + // Wait a few ms to see if a uncaught error occurs + await promisify(setTimeout)(20) }) -t.test('collect handled errors on body stream, reject if used later', t => { +t.test('collect handled errors on body stream, reject if used later', async t => { const delay = value => new Promise(resolve => setTimeout(() => resolve(value), 20)) - return fetch(`${base}invalid-content-encoding`).then(delay).then(res => { - t.equal(res.headers.get('content-type'), 'text/plain') - t.rejects(res.text(), { - name: 'FetchError', - code: 'Z_DATA_ERROR', - }) + const res = await fetch(`${base}invalid-content-encoding`).then(delay) + const delayed = await delay(res) + t.equal(delayed.headers.get('content-type'), 'text/plain') + t.rejects(delayed.text(), { + name: 'FetchError', + code: 'Z_DATA_ERROR', }) }) @@ -793,16 +802,14 @@ t.test('reject body methods immediately with AbortError when aborted before dist }) }) -t.test('raise AbortError when aborted before stream is closed', t => { +t.test('raise AbortError when aborted before stream is closed', async t => { + t.plan(1) const controller = new AbortController() - fetch(`${base}slow`, { signal: controller.signal }) - .then(res => { - res.body.once('error', (err) => { - t.match(err, { name: 'AbortError', code: 'FETCH_ABORT' }) - t.end() - }) - controller.abort() - }) + const res = await fetch(`${base}slow`, { signal: controller.signal }) + res.body.once('error', (err) => { + t.match(err, { name: 'AbortError', code: 'FETCH_ABORT' }) + }) + controller.abort() }) t.test('cancel request body stream with AbortError when aborted', { @@ -831,7 +838,7 @@ t.test('cancel request body stream with AbortError when aborted', { return result }) -t.test('immediately reject when attempting to cancel and unsupported', t => { +t.test('immediately reject when attempting to cancel and unsupported', async t => { const controller = new AbortController() const body = new (class extends Minipass { get destroy () { @@ -839,32 +846,33 @@ t.test('immediately reject when attempting to cancel and unsupported', t => { } })({ objectMode: true }) - return t.rejects(fetch(`${base}slow`, { + await t.rejects(fetch(`${base}slow`, { signal: controller.signal, body, method: 'POST', }), { message: 'not supported' }) }) -t.test('throw TypeError if a signal is not AbortSignal', t => - Promise.all([ - t.rejects(fetch(`${base}inspect`, { signal: {} }), { - name: 'TypeError', - message: /AbortSignal/, - }), - t.rejects(fetch(`${base}inspect`, { signal: '' }), { - name: 'TypeError', - message: /AbortSignal/, - }), - t.rejects(fetch(`${base}inspect`, { signal: Object.create(null) }), { - name: 'TypeError', - message: /AbortSignal/, - }), - ])) +t.test('throw TypeError if a signal is not AbortSignal', async t => { + await t.rejects(fetch(`${base}inspect`, { signal: {} }), { + name: 'TypeError', + message: /AbortSignal/, + }) + await t.rejects(fetch(`${base}inspect`, { signal: '' }), { + name: 'TypeError', + message: /AbortSignal/, + }) + await t.rejects(fetch(`${base}inspect`, { signal: Object.create(null) }), { + name: 'TypeError', + message: /AbortSignal/, + }) +}) -t.test('set default User-Agent', t => - fetch(`${base}inspect`).then(res => res.json()).then(res => - t.match(res.headers['user-agent'], /^minipass-fetch/))) +t.test('set default User-Agent', async t => { + const res = await fetch(`${base}inspect`) + const json = await res.json() + t.match(json.headers['user-agent'], /^minipass-fetch/) +}) t.test('setting User-Agent', t => fetch(`${base}inspect`, { @@ -874,156 +882,163 @@ t.test('setting User-Agent', t => }).then(res => res.json()).then(res => t.equal(res.headers['user-agent'], 'faked'))) -t.test('set default Accept header', t => - fetch(`${base}inspect`).then(res => res.json()).then(res => - t.equal(res.headers.accept, '*/*'))) +t.test('set default Accept header', async t => { + const res = await fetch(`${base}inspect`) + const json = await res.json() + t.equal(json.headers.accept, '*/*') +}) -t.test('allow setting Accept header', t => - fetch(`${base}inspect`, { +t.test('allow setting Accept header', async t => { + const res = await fetch(`${base}inspect`, { headers: { accept: 'application/json', }, - }).then(res => res.json()).then(res => - t.equal(res.headers.accept, 'application/json'))) - -t.test('allow POST request', t => - fetch(`${base}inspect`, { method: 'POST' }) - .then(res => res.json()).then(res => { - t.equal(res.method, 'POST') - t.equal(res.headers['transfer-encoding'], undefined) - t.equal(res.headers['content-type'], undefined) - t.equal(res.headers['content-length'], '0') - })) + }) + const json = await res.json() + t.equal(json.headers.accept, 'application/json') +}) -t.test('POST request with string body', t => - fetch(`${base}inspect`, { +t.test('allow POST request', async t => { + const res = await fetch(`${base}inspect`, { method: 'POST' }) + const json = await res.json() + t.equal(json.method, 'POST') + t.equal(json.headers['transfer-encoding'], undefined) + t.equal(json.headers['content-type'], undefined) + t.equal(json.headers['content-length'], '0') +}) + +t.test('POST request with string body', async t => { + const res = await fetch(`${base}inspect`, { method: 'POST', body: 'a=1', - }).then(res => res.json()).then(res => { - t.equal(res.method, 'POST') - t.equal(res.body, 'a=1') - t.equal(res.headers['transfer-encoding'], undefined) - t.equal(res.headers['content-type'], 'text/plain;charset=UTF-8') - t.equal(res.headers['content-length'], '3') - })) + }) + const json = await res.json() + t.equal(json.method, 'POST') + t.equal(json.body, 'a=1') + t.equal(json.headers['transfer-encoding'], undefined) + t.equal(json.headers['content-type'], 'text/plain;charset=UTF-8') + t.equal(json.headers['content-length'], '3') +}) -t.test('POST request with buffer body', t => - fetch(`${base}inspect`, { +t.test('POST request with buffer body', async t => { + const res = await fetch(`${base}inspect`, { method: 'POST', body: Buffer.from('a=1', 'utf-8'), - }).then(res => res.json()).then(res => { - t.equal(res.method, 'POST') - t.equal(res.body, 'a=1') - t.equal(res.headers['transfer-encoding'], undefined) - t.equal(res.headers['content-type'], undefined) - t.equal(res.headers['content-length'], '3') - })) + }) + const json = await res.json() + t.equal(json.method, 'POST') + t.equal(json.body, 'a=1') + t.equal(json.headers['transfer-encoding'], undefined) + t.equal(json.headers['content-type'], undefined) + t.equal(json.headers['content-length'], '3') +}) -t.test('allow POST request with ArrayBuffer body', t => - fetch(`${base}inspect`, { +t.test('allow POST request with ArrayBuffer body', async t => { + const res = await fetch(`${base}inspect`, { method: 'POST', body: stringToArrayBuffer('Hello, world!\n'), - }).then(res => res.json()).then(res => { - t.equal(res.method, 'POST') - t.equal(res.body, 'Hello, world!\n') - t.equal(res.headers['transfer-encoding'], undefined) - t.equal(res.headers['content-type'], undefined) - t.equal(res.headers['content-length'], '14') - })) + }) + const json = await res.json() + t.equal(json.method, 'POST') + t.equal(json.body, 'Hello, world!\n') + t.equal(json.headers['transfer-encoding'], undefined) + t.equal(json.headers['content-type'], undefined) + t.equal(json.headers['content-length'], '14') +}) -t.test('POST request with ArrayBuffer body from VM context', t => { +t.test('POST request with ArrayBuffer body from VM context', async t => { Buffer.from(new VMArrayBuffer()) const url = `${base}inspect` const opts = { method: 'POST', body: new VMUint8Array(Buffer.from('Hello, world!\n')).buffer, } - return fetch(url, opts).then(res => res.json()).then(res => { - t.equal(res.method, 'POST') - t.equal(res.body, 'Hello, world!\n') - t.equal(res.headers['transfer-encoding'], undefined) - t.equal(res.headers['content-type'], undefined) - t.equal(res.headers['content-length'], '14') - }) + const res = await fetch(url, opts) + const json = await res.json() + t.equal(json.method, 'POST') + t.equal(json.body, 'Hello, world!\n') + t.equal(json.headers['transfer-encoding'], undefined) + t.equal(json.headers['content-type'], undefined) + t.equal(json.headers['content-length'], '14') }) -t.test('POST request with ArrayBufferView (Uint8Array) body', t => { +t.test('POST request with ArrayBufferView (Uint8Array) body', async t => { const url = `${base}inspect` const opts = { method: 'POST', body: new Uint8Array(stringToArrayBuffer('Hello, world!\n')), } - return fetch(url, opts).then(res => res.json()).then(res => { - t.equal(res.method, 'POST') - t.equal(res.body, 'Hello, world!\n') - t.equal(res.headers['transfer-encoding'], undefined) - t.equal(res.headers['content-type'], undefined) - t.equal(res.headers['content-length'], '14') - }) + const res = await fetch(url, opts) + const json = await res.json() + t.equal(json.method, 'POST') + t.equal(json.body, 'Hello, world!\n') + t.equal(json.headers['transfer-encoding'], undefined) + t.equal(json.headers['content-type'], undefined) + t.equal(json.headers['content-length'], '14') }) -t.test('POST request with ArrayBufferView (DataView) body', t => { +t.test('POST request with ArrayBufferView (DataView) body', async t => { const url = `${base}inspect` const opts = { method: 'POST', body: new DataView(stringToArrayBuffer('Hello, world!\n')), } - return fetch(url, opts).then(res => res.json()).then(res => { - t.equal(res.method, 'POST') - t.equal(res.body, 'Hello, world!\n') - t.equal(res.headers['transfer-encoding'], undefined) - t.equal(res.headers['content-type'], undefined) - t.equal(res.headers['content-length'], '14') - }) + const res = await fetch(url, opts) + const json = await res.json() + t.equal(json.method, 'POST') + t.equal(json.body, 'Hello, world!\n') + t.equal(json.headers['transfer-encoding'], undefined) + t.equal(json.headers['content-type'], undefined) + t.equal(json.headers['content-length'], '14') }) -t.test('POST with ArrayBufferView (Uint8Array) body from a VM context', t => { +t.test('POST with ArrayBufferView (Uint8Array) body from a VM context', async t => { Buffer.from(new VMArrayBuffer()) const url = `${base}inspect` const opts = { method: 'POST', body: new VMUint8Array(Buffer.from('Hello, world!\n')), } - return fetch(url, opts).then(res => res.json()).then(res => { - t.equal(res.method, 'POST') - t.equal(res.body, 'Hello, world!\n') - t.equal(res.headers['transfer-encoding'], undefined) - t.equal(res.headers['content-type'], undefined) - t.equal(res.headers['content-length'], '14') - }) + const res = await fetch(url, opts) + const json = await res.json() + t.equal(json.method, 'POST') + t.equal(json.body, 'Hello, world!\n') + t.equal(json.headers['transfer-encoding'], undefined) + t.equal(json.headers['content-type'], undefined) + t.equal(json.headers['content-length'], '14') }) -t.test('POST with ArrayBufferView (Uint8Array, offset, length) body', t => { +t.test('POST with ArrayBufferView (Uint8Array, offset, length) body', async t => { const url = `${base}inspect` const opts = { method: 'POST', body: new Uint8Array(stringToArrayBuffer('Hello, world!\n'), 7, 6), } - return fetch(url, opts).then(res => res.json()).then(res => { - t.equal(res.method, 'POST') - t.equal(res.body, 'world!') - t.equal(res.headers['transfer-encoding'], undefined) - t.equal(res.headers['content-type'], undefined) - t.equal(res.headers['content-length'], '6') - }) + const res = await fetch(url, opts) + const json = await res.json() + t.equal(json.method, 'POST') + t.equal(json.body, 'world!') + t.equal(json.headers['transfer-encoding'], undefined) + t.equal(json.headers['content-type'], undefined) + t.equal(json.headers['content-length'], '6') }) -t.test('POST with blob body without type', t => { +t.test('POST with blob body without type', async t => { const url = `${base}inspect` const opts = { method: 'POST', body: new Blob(['a=1']), } - return fetch(url, opts).then(res => res.json()).then(res => { - t.equal(res.method, 'POST') - t.equal(res.body, 'a=1') - t.equal(res.headers['transfer-encoding'], undefined) - t.equal(res.headers['content-type'], undefined) - t.equal(res.headers['content-length'], '3') - }) + const res = await fetch(url, opts) + const json = await res.json() + t.equal(json.method, 'POST') + t.equal(json.body, 'a=1') + t.equal(json.headers['transfer-encoding'], undefined) + t.equal(json.headers['content-type'], undefined) + t.equal(json.headers['content-length'], '3') }) -t.test('POST with blob body with type', t => { +t.test('POST with blob body with type', async t => { const url = `${base}inspect` const opts = { method: 'POST', @@ -1031,18 +1046,16 @@ t.test('POST with blob body with type', t => { type: 'text/plain;charset=UTF-8', }), } - return fetch(url, opts).then(res => { - return res.json() - }).then(res => { - t.equal(res.method, 'POST') - t.equal(res.body, 'a=1') - t.equal(res.headers['transfer-encoding'], undefined) - t.equal(res.headers['content-type'], 'text/plain;charset=utf-8') - t.equal(res.headers['content-length'], '3') - }) + const res = await fetch(url, opts) + const json = await res.json() + t.equal(json.method, 'POST') + t.equal(json.body, 'a=1') + t.equal(json.headers['transfer-encoding'], undefined) + t.equal(json.headers['content-type'], 'text/plain;charset=utf-8') + t.equal(json.headers['content-length'], '3') }) -t.test('POST with readable stream as body', t => { +t.test('POST with readable stream as body', async t => { const body = new Minipass() body.pause() body.end('a=1') @@ -1055,16 +1068,16 @@ t.test('POST with readable stream as body', t => { method: 'POST', body: body.pipe(new Minipass()), } - return fetch(url, opts).then(res => res.json()).then(res => { - t.equal(res.method, 'POST') - t.equal(res.body, 'a=1') - t.equal(res.headers['transfer-encoding'], 'chunked') - t.equal(res.headers['content-type'], undefined) - t.equal(res.headers['content-length'], undefined) - }) + const res = await fetch(url, opts) + const json = await res.json() + t.equal(json.method, 'POST') + t.equal(json.body, 'a=1') + t.equal(json.headers['transfer-encoding'], 'chunked') + t.equal(json.headers['content-type'], undefined) + t.equal(json.headers['content-length'], undefined) }) -t.test('POST with form-data as body', t => { +t.test('POST with form-data as body', async t => { const form = new FormData() form.append('a', '1') @@ -1073,17 +1086,15 @@ t.test('POST with form-data as body', t => { method: 'POST', body: form, } - return fetch(url, opts).then(res => { - return res.json() - }).then(res => { - t.equal(res.method, 'POST') - t.match(res.headers['content-type'], /^multipart\/form-data;boundary=/) - t.match(res.headers['content-length'], String) - t.equal(res.body, 'a=1') - }) + const res = await fetch(url, opts) + const json = await res.json() + t.equal(json.method, 'POST') + t.match(json.headers['content-type'], /^multipart\/form-data;boundary=/) + t.match(json.headers['content-length'], String) + t.equal(json.body, 'a=1') }) -t.test('POST with form-data using stream as body', t => { +t.test('POST with form-data using stream as body', async t => { t.teardown(() => { const root = path.dirname(__dirname) // parted's multipart form parser writes a temporary file to disk, this removes it @@ -1102,17 +1113,15 @@ t.test('POST with form-data using stream as body', t => { body: form, } - return fetch(url, opts).then(res => { - return res.json() - }).then(res => { - t.equal(res.method, 'POST') - t.match(res.headers['content-type'], /^multipart\/form-data;boundary=/) - t.equal(res.headers['content-length'], undefined) - t.match(res.body, 'my_field=') - }) + const res = await fetch(url, opts) + const json = await res.json() + t.equal(json.method, 'POST') + t.match(json.headers['content-type'], /^multipart\/form-data;boundary=/) + t.equal(json.headers['content-length'], undefined) + t.match(json.body, 'my_field=') }) -t.test('POST with form-data as body and custom headers', t => { +t.test('POST with form-data as body and custom headers', async t => { const form = new FormData() form.append('a', '1') @@ -1125,32 +1134,28 @@ t.test('POST with form-data as body and custom headers', t => { body: form, headers, } - return fetch(url, opts).then(res => { - return res.json() - }).then(res => { - t.equal(res.method, 'POST') - t.match(res.headers['content-type'], /multipart\/form-data; boundary=/) - t.match(res.headers['content-length'], String) - t.equal(res.headers.b, '2') - t.equal(res.body, 'a=1') - }) + const res = await fetch(url, opts) + const json = await res.json() + t.equal(json.method, 'POST') + t.match(json.headers['content-type'], /multipart\/form-data; boundary=/) + t.match(json.headers['content-length'], String) + t.equal(json.headers.b, '2') + t.equal(json.body, 'a=1') }) -t.test('POST with object body', t => { +t.test('POST with object body', async t => { const url = `${base}inspect` // note that fetch simply calls tostring on an object const opts = { method: 'POST', body: { a: 1 }, } - return fetch(url, opts).then(res => { - return res.json() - }).then(res => { - t.equal(res.method, 'POST') - t.equal(res.body, '[object Object]') - t.equal(res.headers['content-type'], 'text/plain;charset=UTF-8') - t.equal(res.headers['content-length'], '15') - }) + const res = await fetch(url, opts) + const json = await res.json() + t.equal(json.method, 'POST') + t.equal(json.body, '[object Object]') + t.equal(json.headers['content-type'], 'text/plain;charset=UTF-8') + t.equal(json.headers['content-length'], '15') }) const uspOpt = { @@ -1171,25 +1176,24 @@ t.test('constructing a Request with URLSearchParams as body has a Content-Type', t.equal(req.headers.get('Content-Type'), 'application/x-www-form-urlencoded;charset=UTF-8') }) -t.test('Reading a body with URLSearchParams should echo back the result', uspOpt, t => { +t.test('Reading a body with URLSearchParams should echo back the result', uspOpt, async t => { const params = new URLSearchParams() params.append('a', '1') - return new Response(params).text().then(text => { - t.equal(text, 'a=1') - }) + const text = await new Response(params).text() + t.equal(text, 'a=1') }) // Body should been cloned... -t.test('Request/Response with URLSearchParams and mutation should not affected body', uspOpt, t => { +// eslint-disable-next-line max-len +t.test('Request/Response with URLSearchParams and mutation should not affected body', uspOpt, async t => { const params = new URLSearchParams() const req = new Request(`${base}inspect`, { method: 'POST', body: params }) params.append('a', '1') - return req.text().then(text => { - t.equal(text, '') - }) + const text = await req.text() + t.equal(text, '') }) -t.test('POST with URLSearchParams as body', uspOpt, t => { +t.test('POST with URLSearchParams as body', uspOpt, async t => { const params = new URLSearchParams() params.append('a', '1') @@ -1198,17 +1202,15 @@ t.test('POST with URLSearchParams as body', uspOpt, t => { method: 'POST', body: params, } - return fetch(url, opts).then(res => { - return res.json() - }).then(res => { - t.equal(res.method, 'POST') - t.equal(res.headers['content-type'], 'application/x-www-form-urlencoded;charset=UTF-8') - t.equal(res.headers['content-length'], '3') - t.equal(res.body, 'a=1') - }) + const res = await fetch(url, opts) + const json = await res.json() + t.equal(json.method, 'POST') + t.equal(json.headers['content-type'], 'application/x-www-form-urlencoded;charset=UTF-8') + t.equal(json.headers['content-length'], '3') + t.equal(json.body, 'a=1') }) -t.test('recognize URLSearchParams when extended', uspOpt, t => { +t.test('recognize URLSearchParams when extended', uspOpt, async t => { class CustomSearchParams extends URLSearchParams {} const params = new CustomSearchParams() params.append('a', '1') @@ -1218,19 +1220,17 @@ t.test('recognize URLSearchParams when extended', uspOpt, t => { method: 'POST', body: params, } - return fetch(url, opts).then(res => { - return res.json() - }).then(res => { - t.equal(res.method, 'POST') - t.equal(res.headers['content-type'], 'application/x-www-form-urlencoded;charset=UTF-8') - t.equal(res.headers['content-length'], '3') - t.equal(res.body, 'a=1') - }) + const res = await fetch(url, opts) + const json = await res.json() + t.equal(json.method, 'POST') + t.equal(json.headers['content-type'], 'application/x-www-form-urlencoded;charset=UTF-8') + t.equal(json.headers['content-length'], '3') + t.equal(json.body, 'a=1') }) /* for 100% code coverage, checks for duck-typing-only detection * where both constructor.name and brand tests fail */ -t.test('recognize URLSearchParams when extended from polyfill', t => { +t.test('recognize URLSearchParams when extended from polyfill', async t => { class CustomPolyfilledSearchParams extends URLSearchParamsPolyfill {} const params = new CustomPolyfilledSearchParams() params.append('a', '1') @@ -1240,17 +1240,15 @@ t.test('recognize URLSearchParams when extended from polyfill', t => { method: 'POST', body: params, } - return fetch(url, opts).then(res => { - return res.json() - }).then(res => { - t.equal(res.method, 'POST') - t.equal(res.headers['content-type'], 'application/x-www-form-urlencoded;charset=UTF-8') - t.equal(res.headers['content-length'], '3') - t.equal(res.body, 'a=1') - }) + const res = await fetch(url, opts) + const json = await res.json() + t.equal(json.method, 'POST') + t.equal(json.headers['content-type'], 'application/x-www-form-urlencoded;charset=UTF-8') + t.equal(json.headers['content-length'], '3') + t.equal(json.body, 'a=1') }) -t.test('overwrite Content-Length if possible', t => { +t.test('overwrite Content-Length if possible', async t => { const url = `${base}inspect` // note that fetch simply calls tostring on an object const opts = { @@ -1260,150 +1258,131 @@ t.test('overwrite Content-Length if possible', t => { }, body: 'a=1', } - return fetch(url, opts).then(res => { - return res.json() - }).then(res => { - t.equal(res.method, 'POST') - t.equal(res.body, 'a=1') - t.equal(res.headers['transfer-encoding'], undefined) - t.equal(res.headers['content-type'], 'text/plain;charset=UTF-8') - t.equal(res.headers['content-length'], '3') - }) + const res = await fetch(url, opts) + const json = await res.json() + t.equal(json.method, 'POST') + t.equal(json.body, 'a=1') + t.equal(json.headers['transfer-encoding'], undefined) + t.equal(json.headers['content-type'], 'text/plain;charset=UTF-8') + t.equal(json.headers['content-length'], '3') }) -t.test('PUT', t => { +t.test('PUT', async t => { const url = `${base}inspect` const opts = { method: 'PUT', body: 'a=1', } - return fetch(url, opts).then(res => { - return res.json() - }).then(res => { - t.equal(res.method, 'PUT') - t.equal(res.body, 'a=1') - }) + const res = await fetch(url, opts) + const json = await res.json() + t.equal(json.method, 'PUT') + t.equal(json.body, 'a=1') }) -t.test('DELETE', t => { +t.test('DELETE', async t => { const url = `${base}inspect` const opts = { method: 'DELETE', } - return fetch(url, opts).then(res => { - return res.json() - }).then(res => { - t.equal(res.method, 'DELETE') - }) + const res = await fetch(url, opts) + const json = await res.json() + t.equal(json.method, 'DELETE') }) -t.test('DELETE with string body', t => { +t.test('DELETE with string body', async t => { const url = `${base}inspect` const opts = { method: 'DELETE', body: 'a=1', } - return fetch(url, opts).then(res => { - return res.json() - }).then(res => { - t.equal(res.method, 'DELETE') - t.equal(res.body, 'a=1') - t.equal(res.headers['transfer-encoding'], undefined) - t.equal(res.headers['content-length'], '3') - }) + const res = await fetch(url, opts) + const json = await res.json() + t.equal(json.method, 'DELETE') + t.equal(json.body, 'a=1') + t.equal(json.headers['transfer-encoding'], undefined) + t.equal(json.headers['content-length'], '3') }) -t.test('PATCH', t => { +t.test('PATCH', async t => { const url = `${base}inspect` const opts = { method: 'PATCH', body: 'a=1', } - return fetch(url, opts).then(res => { - return res.json() - }).then(res => { - t.equal(res.method, 'PATCH') - t.equal(res.body, 'a=1') - }) + const res = await fetch(url, opts) + const json = await res.json() + t.equal(json.method, 'PATCH') + t.equal(json.body, 'a=1') }) -t.test('HEAD', t => { +t.test('HEAD', async t => { const url = `${base}hello` const opts = { method: 'HEAD', } - return fetch(url, opts).then(res => { - t.equal(res.status, 200) - t.equal(res.statusText, 'OK') - t.equal(res.headers.get('content-type'), 'text/plain') - t.match(res.body, Minipass) - return res.text() - }).then(text => { - t.equal(text, '') - }) + const res = await fetch(url, opts) + t.equal(res.status, 200) + t.equal(res.statusText, 'OK') + t.equal(res.headers.get('content-type'), 'text/plain') + t.match(res.body, Minipass) + const text = await res.text() + t.equal(text, '') }) -t.test('HEAD with content-encoding header', t => { +t.test('HEAD with content-encoding header', async t => { const url = `${base}error/404` const opts = { method: 'HEAD', } - return fetch(url, opts).then(res => { - t.equal(res.status, 404) - t.equal(res.headers.get('content-encoding'), 'gzip') - return res.text() - }).then(text => { - t.equal(text, '') - }) + const res = await fetch(url, opts) + t.equal(res.status, 404) + t.equal(res.headers.get('content-encoding'), 'gzip') + const text = await res.text() + t.equal(text, '') }) -t.test('OPTIONS', t => { +t.test('OPTIONS', async t => { const url = `${base}options` const opts = { method: 'OPTIONS', } - return fetch(url, opts).then(res => { - t.equal(res.status, 200) - t.equal(res.statusText, 'OK') - t.equal(res.headers.get('allow'), 'GET, HEAD, OPTIONS') - t.match(res.body, Minipass) - }) + const res = await fetch(url, opts) + t.equal(res.status, 200) + t.equal(res.statusText, 'OK') + t.equal(res.headers.get('allow'), 'GET, HEAD, OPTIONS') + t.match(res.body, Minipass) }) -t.test('reject decoding body twice', t => { +t.test('reject decoding body twice', async t => { const url = `${base}plain` - return fetch(url).then(res => { - t.equal(res.headers.get('content-type'), 'text/plain') - return res.text().then(result => { - t.equal(res.bodyUsed, true) - return t.rejects(res.text()) - }) - }) + const res = await fetch(url) + t.equal(res.headers.get('content-type'), 'text/plain') + await res.text() + t.equal(res.bodyUsed, true) + t.rejects(res.text()) }) -t.test('response trailers', t => - fetch(`${base}trailers`).then(res => { - t.equal(res.status, 200) - t.equal(res.statusText, 'OK') - t.equal(res.headers.get('Trailer'), 'X-Node-Fetch') - return res.trailer.then(trailers => { - t.same(Array.from(trailers.keys()), ['x-node-fetch']) - t.equal(trailers.get('x-node-fetch'), 'hello world!') - }) - })) +t.test('response trailers', async t => { + const res = await fetch(`${base}trailers`) + t.equal(res.status, 200) + t.equal(res.statusText, 'OK') + t.equal(res.headers.get('Trailer'), 'X-Node-Fetch') + const trailers = await res.trailer + t.same(Array.from(trailers.keys()), ['x-node-fetch']) + t.equal(trailers.get('x-node-fetch'), 'hello world!') +}) -t.test('maximum response size, multiple chunk', t => { +t.test('maximum response size, multiple chunk', async t => { const url = `${base}size/chunk` const opts = { size: 5, } - return fetch(url, opts).then(res => { - t.equal(res.status, 200) - t.equal(res.headers.get('content-type'), 'text/plain') - return t.rejects(res.text(), { - name: 'FetchError', - type: 'max-size', - }) + const res = await fetch(url, opts) + t.equal(res.status, 200) + t.equal(res.headers.get('content-type'), 'text/plain') + await t.rejects(res.text(), { + name: 'FetchError', + type: 'max-size', }) }) @@ -1422,177 +1401,148 @@ t.test('maximum response size, single chunk', t => { }) }) -t.test('pipe response body as stream', t => { +t.test('pipe response body as stream', async t => { + t.plan(2) const url = `${base}hello` - return fetch(url).then(res => { - t.match(res.body, Minipass) - return streamToPromise(res.body, chunk => { - if (chunk === null) { - return - } - t.equal(chunk.toString(), 'world') - }) + const res = await fetch(url) + t.match(res.body, Minipass) + await streamToPromise(res.body, chunk => { + if (chunk === null) { + return + } + t.equal(chunk.toString(), 'world') }) }) -t.test('clone a response, and use both as stream', t => { +t.test('clone a response, and use both as stream', async t => { + t.plan(4) const url = `${base}hello` - return fetch(url).then(res => { - const r1 = res.clone() - t.match(res.body, Minipass) - t.match(r1.body, Minipass) - const dataHandler = chunk => { - if (chunk === null) { - return - } - t.equal(chunk.toString(), 'world') + const res = await fetch(url) + const cloned = res.clone() + t.match(res.body, Minipass) + t.match(cloned.body, Minipass) + const dataHandler = chunk => { + if (chunk === null) { + return } + t.equal(chunk.toString(), 'world') + } - return Promise.all([ - streamToPromise(res.body, dataHandler), - streamToPromise(r1.body, dataHandler), - ]) - }) + await Promise.all([ + streamToPromise(res.body, dataHandler), + streamToPromise(cloned.body, dataHandler), + ]) }) -t.test('clone a json response and log it as text response', t => { +t.test('clone a json response and log it as text response', async t => { const url = `${base}json` - return fetch(url).then(res => { - const r1 = res.clone() - return Promise.all([res.json(), r1.text()]).then(results => { - t.same(results[0], { name: 'value' }) - t.equal(results[1], '{"name":"value"}') - }) - }) + const res = await fetch(url) + const cloned = res.clone() + const results = await Promise.all([res.json(), cloned.text()]) + t.same(results[0], { name: 'value' }) + t.equal(results[1], '{"name":"value"}') }) -t.test('clone a json response, and then log it as text response', t => { +t.test('clone a json response, and then log it as text response', async t => { const url = `${base}json` - return fetch(url).then(res => { - const r1 = res.clone() - return res.json().then(jsonResult => { - t.same(jsonResult, { name: 'value' }) - return r1.text().then(textResult => { - t.equal(textResult, '{"name":"value"}') - }) - }) - }) + const res = await fetch(url) + const cloned = res.clone() + const jsonResult = await res.json() + t.same(jsonResult, { name: 'value' }) + const textResult = await cloned.text() + t.equal(textResult, '{"name":"value"}') }) -t.test('clone a json response, first log as text response, then return json object', t => { +t.test('clone a json response, first log as text response, then return json object', async t => { const url = `${base}json` - return fetch(url).then(res => { - const r1 = res.clone() - return r1.text().then(textResult => { - t.equal(textResult, '{"name":"value"}') - return res.json().then(jsonResult => { - t.same(jsonResult, { name: 'value' }) - }) - }) - }) + const res = await fetch(url) + const cloned = res.clone() + const textResult = await cloned.text() + t.equal(textResult, '{"name":"value"}') + const jsonResult = await res.json() + t.same(jsonResult, { name: 'value' }) }) -t.test('do not allow cloning a response after its been used', t => { +t.test('do not allow cloning a response after its been used', async t => { const url = `${base}hello` - return fetch(url).then(res => - res.text().then(result => { - t.throws(() => res.clone()) - }) - ) + const res = await fetch(url) + await res.text() + t.throws(() => res.clone()) }) -t.test('get all responses of a header', t => { +t.test('get all responses of a header', async t => { const url = `${base}cookie` - return fetch(url).then(res => { - const expected = 'a=1, b=1' - t.equal(res.headers.get('set-cookie'), expected) - t.equal(res.headers.get('Set-Cookie'), expected) - }) + const res = await fetch(url) + const expected = 'a=1, b=1' + t.equal(res.headers.get('set-cookie'), expected) + t.equal(res.headers.get('Set-Cookie'), expected) }) -t.test('return all headers using raw()', t => { +t.test('return all headers using raw()', async t => { const url = `${base}cookie` - return fetch(url).then(res => { - const expected = [ - 'a=1', - 'b=1', - ] - - t.same(res.headers.raw()['set-cookie'], expected) - }) + const res = await fetch(url) + t.same(res.headers.raw()['set-cookie'], ['a=1', 'b=1']) }) -t.test('delete header', t => { +t.test('delete header', async t => { const url = `${base}cookie` - return fetch(url).then(res => { - res.headers.delete('set-cookie') - t.equal(res.headers.get('set-cookie'), null) - }) + const res = await fetch(url) + res.headers.delete('set-cookie') + t.equal(res.headers.get('set-cookie'), null) }) -t.test('send request with connection keep-alive if agent is provided', t => { +t.test('send request with connection keep-alive if agent is provided', async t => { const url = `${base}inspect` const opts = { agent: new http.Agent({ keepAlive: true, }), } - return fetch(url, opts).then(res => { - return res.json() - }).then(res => { - t.equal(res.headers.connection, 'keep-alive') - }) + const res = await fetch(url, opts) + const body = await res.json() + t.equal(body.headers.connection, 'keep-alive') }) -t.test('fetch with Request instance', t => { +t.test('fetch with Request instance', async t => { const url = `${base}hello` const req = new Request(url) - return fetch(req).then(res => { - t.equal(res.url, url) - t.equal(res.ok, true) - t.equal(res.status, 200) - }) + const res = await fetch(req) + t.equal(res.url, url) + t.equal(res.ok, true) + t.equal(res.status, 200) }) -t.test('fetch with Node.js legacy URL object', t => { +t.test('fetch with Node.js legacy URL object', async t => { const url = `${base}hello` const urlObj = parseURL(url) const req = new Request(urlObj) - return fetch(req).then(res => { - t.equal(res.url, url) - t.equal(res.ok, true) - t.equal(res.status, 200) - }) + const res = await fetch(req) + t.equal(res.url, url) + t.equal(res.ok, true) + t.equal(res.status, 200) }) -t.test('fetch with Node.js URL object', t => { +t.test('fetch with Node.js URL object', async t => { const url = `${base}hello` const urlObj = new URL(url) const req = new Request(urlObj) - return fetch(req).then(res => { - t.equal(res.url, url) - t.equal(res.ok, true) - t.equal(res.status, 200) - }) + const res = await fetch(req) + t.equal(res.url, url) + t.equal(res.ok, true) + t.equal(res.status, 200) }) -t.test('reading blob as text', t => { - return new Response(`hello`) - .blob() - .then(blob => blob.text()) - .then(body => { - t.equal(body, 'hello') - }) +t.test('reading blob as text', async t => { + const blob = await new Response(`hello`).blob() + const body = await blob.text() + t.equal(body, 'hello') }) -t.test('reading blob as arrayBuffer', t => { - return new Response(`hello`) - .blob() - .then(blob => blob.arrayBuffer()) - .then(ab => { - const str = String.fromCharCode.apply(null, new Uint8Array(ab)) - t.equal(str, 'hello') - }) +t.test('reading blob as arrayBuffer', async t => { + const blob = await new Response(`hello`).blob() + const ab = await blob.arrayBuffer() + const str = String.fromCharCode.apply(null, new Uint8Array(ab)) + t.equal(str, 'hello') }) t.test('reading blob as stream', t => { @@ -1604,27 +1554,25 @@ t.test('reading blob as stream', t => { })) }) -t.test('blob round-trip', t => { +t.test('blob round-trip', async t => { const url = `${base}hello` - let length, type - - return fetch(url).then(res => res.blob()).then(blob => { - const inspectUrl = `${base}inspect` - length = blob.size - type = blob.type - return fetch(inspectUrl, { - method: 'POST', - body: blob, - }) - }).then(res => res.json()).then(({ body, headers }) => { - t.equal(body, 'world') - t.equal(headers['content-type'], type) - t.equal(headers['content-length'], String(length)) + const res = await fetch(url) + const blob = await res.blob() + const inspectUrl = `${base}inspect` + const length = blob.size + const type = blob.type + const res2 = await fetch(inspectUrl, { + method: 'POST', + body: blob, }) + const { body, headers } = await res2.json() + t.equal(body, 'world') + t.equal(headers['content-type'], type) + t.equal(headers['content-length'], String(length)) }) -t.test('overwrite Request instance', t => { +t.test('overwrite Request instance', async t => { const url = `${base}inspect` const req = new Request(url, { method: 'POST', @@ -1632,17 +1580,15 @@ t.test('overwrite Request instance', t => { a: '1', }, }) - return fetch(req, { + const res = await fetch(req, { method: 'GET', headers: { a: '2', }, - }).then(res => { - return res.json() - }).then(body => { - t.equal(body.method, 'GET') - t.equal(body.headers.a, '2') }) + const body = await res.json() + t.equal(body.method, 'GET') + t.equal(body.headers.a, '2') }) t.test('arrayBuffer(), blob(), text(), json() and buffer() method in Body constructor', t => { @@ -1655,42 +1601,40 @@ t.test('arrayBuffer(), blob(), text(), json() and buffer() method in Body constr t.end() }) -t.test('https request', { timeout: 5000 }, t => { +t.test('https request', { timeout: 5000 }, async t => { const url = 'https://github.com/' const opts = { method: 'HEAD', } - return fetch(url, opts).then(res => { - t.equal(res.status, 200) - t.equal(res.ok, true) - }) + const res = await fetch(url, opts) + t.equal(res.status, 200) + t.equal(res.ok, true) }) // issue #414 -t.test('reject if attempt to accumulate body stream throws', t => { +t.test('reject if attempt to accumulate body stream throws', async t => { const body = new Minipass() body.pause() body.end('a=1') setTimeout(() => body.resume(), 100) const res = new Response(body.pipe(new Minipass())) const bufferConcat = Buffer.concat - const restoreBufferConcat = () => Buffer.concat = bufferConcat Buffer.concat = () => { throw new Error('embedded error') } - const textPromise = res.text() - // Ensure that `Buffer.concat` is always restored: - textPromise.then(restoreBufferConcat, restoreBufferConcat) + t.teardown(() => { + Buffer.concat = bufferConcat + }) - return t.rejects(textPromise, { + return t.rejects(res.text(), { name: 'FetchError', type: 'system', message: /embedded error/, }) }) -t.test('supports supplying a lookup function to the agent', t => { +t.test('supports supplying a lookup function to the agent', async t => { const url = `${base}redirect/301` let called = 0 function lookupSpy (hostname, options, callback) { @@ -1698,12 +1642,11 @@ t.test('supports supplying a lookup function to the agent', t => { return lookup(hostname, options, callback) } const agent = http.Agent({ lookup: lookupSpy }) - return fetch(url, { agent }).then(() => { - t.equal(called, 2) - }) + await fetch(url, { agent }) + t.equal(called, 2) }) -t.test('supports supplying a famliy option to the agent', t => { +t.test('supports supplying a famliy option to the agent', async t => { const url = `${base}redirect/301` const families = [] const family = Symbol('family') @@ -1712,12 +1655,11 @@ t.test('supports supplying a famliy option to the agent', t => { return lookup(hostname, {}, callback) } const agent = http.Agent({ lookup: lookupSpy, family }) - return fetch(url, { agent }).then(() => { - t.same(families, [family, family]) - }) + await fetch(url, { agent }) + t.same(families, [family, family]) }) -t.test('function supplying the agent', t => { +t.test('function supplying the agent', async t => { const url = `${base}inspect` const agent = new http.Agent({ @@ -1726,19 +1668,17 @@ t.test('function supplying the agent', t => { let parsedURL - return fetch(url, { + const res = await fetch(url, { agent: function (_parsedURL) { parsedURL = _parsedURL return agent }, - }).then(res => { - return res.json() - }).then(res => { - // the agent provider should have been called - t.equal(parsedURL.protocol, 'http:') - // the agent we returned should have been used - t.equal(res.headers.connection, 'keep-alive') }) + const body = await res.json() + // the agent provider should have been called + t.equal(parsedURL.protocol, 'http:') + // the agent we returned should have been used + t.equal(body.headers.connection, 'keep-alive') }) t.test('calculate content length and extract content type', t => { @@ -1808,99 +1748,90 @@ t.test('calculate content length and extract content type', t => { }) t.test('with optional `encoding`', t => { - t.test('only use UTF-8 decoding with text()', t => - fetch(`${base}encoding/euc-jp`).then(res => { - t.equal(res.status, 200) - return res.text().then(result => - t.equal(result, '' + - '\ufffd\ufffd\ufffd\u0738\ufffd')) - })) + t.test('only use UTF-8 decoding with text()', async t => { + const res = await fetch(`${base}encoding/euc-jp`) + t.equal(res.status, 200) + const result = await res.text() + t.equal(result, '' + + '\ufffd\ufffd\ufffd\u0738\ufffd') + }) - t.test('encoding decode, xml dtd detect', t => - fetch(`${base}encoding/euc-jp`).then(res => { - t.equal(res.status, 200) - return res.textConverted().then(result => - t.equal(result, '日本語')) - })) + t.test('encoding decode, xml dtd detect', async t => { + const res = await fetch(`${base}encoding/euc-jp`) + t.equal(res.status, 200) + const result = await res.textConverted() + t.equal(result, '日本語') + }) - t.test('encoding decode, content-type detect', t => - fetch(`${base}encoding/shift-jis`).then(res => { - t.equal(res.status, 200) - return res.textConverted().then(result => - t.equal(result, '
日本語
')) - })) + t.test('encoding decode, content-type detect', async t => { + const res = await fetch(`${base}encoding/shift-jis`) + t.equal(res.status, 200) + const result = await res.textConverted() + t.equal(result, '
日本語
') + }) - t.test('encoding decode, html5 detect', t => - fetch(`${base}encoding/gbk`).then(res => { - t.equal(res.status, 200) - return res.textConverted().then(result => - t.equal(result, '
中文
')) - })) + t.test('encoding decode, html5 detect', async t => { + const res = await fetch(`${base}encoding/gbk`) + t.equal(res.status, 200) + const result = await res.textConverted() + t.equal(result, '
中文
') + }) - t.test('encoding decode, html4 detect', t => - fetch(`${base}encoding/gb2312`).then(res => { - t.equal(res.status, 200) - return res.textConverted().then(result => { - t.equal(result, '' + - '
中文
') - }) - })) + t.test('encoding decode, html4 detect', async t => { + const res = await fetch(`${base}encoding/gb2312`) + t.equal(res.status, 200) + const result = await res.textConverted() + t.equal(result, '' + + '
中文
') + }) - t.test('encoding decode, html4 detect reverse http-equiv', t => - fetch(`${base}encoding/gb2312-reverse`).then(res => { - t.equal(res.status, 200) - return res.textConverted().then(result => { - t.equal(result, '' + - '
中文
') - }) - })) + t.test('encoding decode, html4 detect reverse http-equiv', async t => { + const res = await fetch(`${base}encoding/gb2312-reverse`) + t.equal(res.status, 200) + const result = await res.textConverted() + t.equal(result, '' + + '
中文
') + }) - t.test('default to utf8 encoding', t => - fetch(`${base}encoding/utf8`).then(res => { - t.equal(res.status, 200) - t.equal(res.headers.get('content-type'), null) - return res.textConverted().then(result => { - t.equal(result, '中文') - }) - })) + t.test('default to utf8 encoding', async t => { + const res = await fetch(`${base}encoding/utf8`) + t.equal(res.status, 200) + t.equal(res.headers.get('content-type'), null) + const result = await res.textConverted() + t.equal(result, '中文') + }) - t.test('uncommon content-type order, charset in front', t => - fetch(`${base}encoding/order1`).then(res => { - t.equal(res.status, 200) - return res.textConverted().then(result => { - t.equal(result, '中文') - }) - })) + t.test('uncommon content-type order, charset in front', async t => { + const res = await fetch(`${base}encoding/order1`) + t.equal(res.status, 200) + const result = await res.textConverted() + t.equal(result, '中文') + }) - t.test('uncommon content-type order, end with qs', t => - fetch(`${base}encoding/order2`).then(res => { - t.equal(res.status, 200) - return res.textConverted().then(result => { - t.equal(result, '中文') - }) - })) + t.test('uncommon content-type order, end with qs', async t => { + const res = await fetch(`${base}encoding/order2`) + t.equal(res.status, 200) + const result = await res.textConverted() + t.equal(result, '中文') + }) - t.test('chunked encoding, html4 detect', t => { + t.test('chunked encoding, html4 detect', async t => { const url = `${base}encoding/chunked` - return fetch(url).then(res => { - t.equal(res.status, 200) - const padding = 'a'.repeat(10) - return res.textConverted().then(result => { - t.equal(result, `${padding}
日本語
') - }) - }) + const res = await fetch(url) + t.equal(res.status, 200) + const padding = 'a'.repeat(10) + const result = await res.textConverted() + t.equal(result, `${padding}
日本語
') }) - t.test('only do encoding detection up to 1024 bytes', t => { + t.test('only do encoding detection up to 1024 bytes', async t => { const url = `${base}encoding/invalid` - return fetch(url).then(res => { - t.equal(res.status, 200) - const padding = 'a'.repeat(1200) - return res.textConverted().then(result => { - t.not(result, `${padding}中文`) - }) - }) + const res = await fetch(url) + t.equal(res.status, 200) + const padding = 'a'.repeat(1200) + const result = await res.textConverted() + t.not(result, `${padding}中文`) }) t.end() @@ -1971,11 +1902,11 @@ t.test('aborting data uris', t => { t.test('cannot abort after first tick', t => { const controller = new Controller() - t.resolves(fetch(url, { signal: controller.signal })) - Promise.resolve().then(() => { + const testPromise = t.resolves(fetch(url, { signal: controller.signal })) + process.nextTick(() => { controller.abort() - t.end() }) + return testPromise }) }) }) diff --git a/test/request.js b/test/request.js index 5e8cb9c..5b7fd41 100644 --- a/test/request.js +++ b/test/request.js @@ -136,18 +136,17 @@ t.test('should support parsing headers', t => { t.end() }) -t.test('should support arrayBuffer() method', t => { +t.test('should support arrayBuffer() method', async t => { const url = base var req = new Request(url, { method: 'POST', body: 'a=1', }) t.equal(req.url, url) - return req.arrayBuffer().then(function (result) { - t.type(result, ArrayBuffer) - const str = String.fromCharCode.apply(null, new Uint8Array(result)) - t.equal(str, 'a=1') - }) + const result = await req.arrayBuffer() + t.type(result, ArrayBuffer) + const str = String.fromCharCode.apply(null, new Uint8Array(result)) + t.equal(str, 'a=1') }) t.test('should support text() method', t => { @@ -180,21 +179,20 @@ t.test('should support buffer() method', t => { return req.buffer().then(result => t.equal(result.toString(), 'a=1')) }) -t.test('should support blob() method', t => { +t.test('should support blob() method', async t => { const url = base var req = new Request(url, { method: 'POST', body: Buffer.from('a=1'), }) t.equal(req.url, url) - return req.blob().then(function (result) { - t.type(result, Blob) - t.equal(result.size, 3) - t.equal(result.type, '') - }) + const result = await req.blob() + t.type(result, Blob) + t.equal(result.size, 3) + t.equal(result.type, '') }) -t.test('should support clone() method', t => { +t.test('should support clone() method', async t => { const url = base const r = new Minipass().end('a=1') r.pause() @@ -227,10 +225,9 @@ t.test('should support clone() method', t => { t.equal(cl.signal, signal) // clone body shouldn't be the same body t.not(cl.body, body) - return Promise.all([cl.text(), req.text()]).then(results => { - t.equal(results[0], 'a=1') - t.equal(results[1], 'a=1') - }) + const results = await Promise.all([cl.text(), req.text()]) + t.equal(results[0], 'a=1') + t.equal(results[1], 'a=1') }) t.test('should support ArrayBuffer as body', t => { diff --git a/test/response.js b/test/response.js index 15ad845..0e636c8 100644 --- a/test/response.js +++ b/test/response.js @@ -69,17 +69,17 @@ t.test('should support buffer() method', t => new Response('a=1').buffer().then(result => t.equal(result.toString(), 'a=1'))) -t.test('should support blob() method', t => - new Response('a=1', { +t.test('should support blob() method', async t => { + const result = await new Response('a=1', { method: 'POST', headers: { 'Content-Type': 'text/plain', }, - }).blob().then(result => { - t.type(result, Blob) - t.equal(result.size, 3) - t.equal(result.type, 'text/plain') - })) + }).blob() + t.type(result, Blob) + t.equal(result.size, 3) + t.equal(result.type, 'text/plain') +}) t.test('should support clone() method', t => { const r = new Minipass().end('a=1') @@ -155,15 +155,14 @@ t.test('should default to empty string as url', t => { t.end() }) -t.test('trailers in response option', t => { +t.test('trailers in response option', async t => { const Headers = require('../lib/headers.js') const res = new Response(null, { trailer: Headers.createHeadersLenient({ 'X-Node-Fetch': 'hello world!', }), }) - return res.trailer.then(trailers => { - t.same(Array.from(trailers.keys()), ['x-node-fetch']) - t.equal(trailers.get('x-node-fetch'), 'hello world!') - }) + const trailers = await res.trailer + t.same(Array.from(trailers.keys()), ['x-node-fetch']) + t.equal(trailers.get('x-node-fetch'), 'hello world!') })