UNPKG

thenavisapp

Version:

This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).

440 lines (350 loc) 12.1 kB
import process from 'node:process'; import {Buffer} from 'node:buffer'; import stream from 'node:stream'; import {pipeline as streamPipeline} from 'node:stream/promises'; import fs from 'node:fs'; import fsPromises from 'node:fs/promises'; import path from 'node:path'; import test from 'ava'; import delay from 'delay'; import {pEvent} from 'p-event'; import type {Handler} from 'express'; import {parse, Body, isBodyFile, type BodyEntryPath, type BodyEntryRawValue} from 'then-busboy'; import {FormData as FormDataNode, Blob, File} from 'formdata-node'; import {fileFromPath} from 'formdata-node/file-from-path'; import getStream from 'get-stream'; import FormData from 'form-data'; import got, {UploadError} from '../source/index.js'; import withServer from './helpers/with-server.js'; const defaultEndpoint: Handler = async (request, response) => { response.setHeader('method', request.method); await streamPipeline(request, response); }; const echoHeaders: Handler = (request, response) => { response.end(JSON.stringify(request.headers)); }; const echoMultipartBody: Handler = async (request, response) => { const body = await parse(request); const entries = await Promise.all( [...body.entries()].map<Promise<[BodyEntryPath, BodyEntryRawValue]>>( async ([name, value]) => [name, isBodyFile(value) ? await value.text() : value], ), ); response.json(Body.json(entries)); }; test('GET cannot have body without the `allowGetBody` option', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); await t.throwsAsync(got.get({body: 'hi'}), {message: 'The `GET` method cannot be used with a body'}); }); test('GET can have body with option allowGetBody', withServer, async (t, server, got) => { server.get('/', defaultEndpoint); await t.notThrowsAsync(got.get({body: 'hi', allowGetBody: true})); }); test('invalid body', async t => { await t.throwsAsync( // @ts-expect-error Error tests got.post('https://example.com', {body: {}}), ); }); test('sends strings', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const {body} = await got.post({body: 'wow'}); t.is(body, 'wow'); }); test('sends Buffers', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const {body} = await got.post({body: Buffer.from('wow')}); t.is(body, 'wow'); }); test('sends Streams', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const {body} = await got.post({body: stream.Readable.from('wow')}); t.is(body, 'wow'); }); test('sends plain objects as forms', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const {body} = await got.post({ form: {such: 'wow'}, }); t.is(body, 'such=wow'); }); test('does not support sending arrays as forms', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); await t.throwsAsync(got.post({ form: ['such', 'wow'], })); }); test('sends plain objects as JSON', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const {body} = await got.post({ json: {such: 'wow'}, responseType: 'json', }); t.deepEqual(body, {such: 'wow'}); }); test('sends arrays as JSON', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const {body} = await got.post({ json: ['such', 'wow'], responseType: 'json', }); t.deepEqual(body, ['such', 'wow']); }); test('works with empty post response', withServer, async (t, server, got) => { server.post('/empty', (_request, response) => { response.end(); }); const {body} = await got.post('empty', {body: 'wow'}); t.is(body, ''); }); test('`content-length` header with string body', withServer, async (t, server, got) => { server.post('/', echoHeaders); const {body} = await got.post({body: 'wow'}); const headers = JSON.parse(body); t.is(headers['content-length'], '3'); }); test('`content-length` header with json body', withServer, async (t, server, got) => { server.post('/', echoHeaders); const {body} = await got.post({json: {foo: 'bar'}}); const headers = JSON.parse(body); t.is(headers['content-length'], '13'); }); test('`content-length` header with form body', withServer, async (t, server, got) => { server.post('/', echoHeaders); const {body} = await got.post({form: {foo: 'bar'}}); const headers = JSON.parse(body); t.is(headers['content-length'], '7'); }); test('`content-length` header with Buffer body', withServer, async (t, server, got) => { server.post('/', echoHeaders); const {body} = await got.post({body: Buffer.from('wow')}); const headers = JSON.parse(body); t.is(headers['content-length'], '3'); }); test('`content-length` header with Stream body', withServer, async (t, server, got) => { server.post('/', echoHeaders); const {body} = await got.post({body: stream.Readable.from('wow')}); const headers = JSON.parse(body); t.is(headers['transfer-encoding'], 'chunked', 'likely failed to get headers at all'); t.is(headers['content-length'], undefined); }); test('`content-length` header is not overriden', withServer, async (t, server, got) => { server.post('/', echoHeaders); const {body} = await got.post({ body: 'wow', headers: { 'content-length': '10', }, }); const headers = JSON.parse(body); t.is(headers['content-length'], '10'); }); test('`content-length` header is present when using custom content-type', withServer, async (t, server, got) => { server.post('/', echoHeaders); const {body} = await got.post({ json: {foo: 'bar'}, headers: { 'content-type': 'custom', }, }); const headers = JSON.parse(body); t.is(headers['content-length'], '13'); }); test('`content-length` header disabled for chunked transfer-encoding', withServer, async (t, server, got) => { server.post('/', echoHeaders); const {body} = await got.post({ body: '3\r\nwow\r\n0\r\n', headers: { 'transfer-encoding': 'chunked', }, }); const headers = JSON.parse(body); t.is(headers['transfer-encoding'], 'chunked', 'likely failed to get headers at all'); t.is(headers['content-length'], undefined); }); test('`content-type` header is not overriden when object in `options.body`', withServer, async (t, server, got) => { server.post('/', echoHeaders); const {body: headers} = await got.post<Record<string, string>>({ headers: { 'content-type': 'doge', }, json: { such: 'wow', }, responseType: 'json', }); t.is(headers['content-type'], 'doge'); }); test('throws when form body is not a plain object or array', async t => { // @ts-expect-error Manual test await t.throwsAsync(got.post('https://example.com', {form: 'such=wow'})); }); // See https://github.com/sindresorhus/got/issues/897 test('the `json` payload is not touched', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const {body} = await got.post<{context: {foo: true}}>({ json: { context: { foo: true, }, }, responseType: 'json', }); t.true('context' in body); t.true(body.context.foo); }); test('the `body` payload is not touched', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const buffer = Buffer.from('Hello, Got!') as Buffer & {context?: unknown}; buffer.context = {foo: 'bar'}; const body = await got.post({body: buffer}).text(); t.is(body, 'Hello, Got!'); }); test('the `form` payload is not touched', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const form = { context: true, }; const body = await got.post({form}).text(); t.is(body, 'context=true'); }); test('DELETE method sends plain objects as JSON', withServer, async (t, server, got) => { server.delete('/', defaultEndpoint); const {body} = await got.delete({ json: {such: 'wow'}, responseType: 'json', }); t.deepEqual(body, {such: 'wow'}); }); test('catches body errors before calling pipeline() - promise', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); await t.throwsAsync(got.post({ body: fs.createReadStream('./file-that-does-not-exist.txt'), }), { message: /ENOENT: no such file or directory/, }); // Wait for unhandled errors await delay(100); }); test('catches body errors before calling pipeline() - stream', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); await t.throwsAsync(getStream(got.stream.post({ body: fs.createReadStream('./file-that-does-not-exist.txt'), })), { message: /ENOENT: no such file or directory/, }); // Wait for unhandled errors await delay(100); }); test('body - file read stream', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const fullPath = path.resolve('test/fixtures/ok'); const toSend = await getStream(fs.createReadStream(fullPath)); const body = await got.post({ body: fs.createReadStream(fullPath), }).text(); t.is(toSend, body); }); test('body - file read stream, wait for `ready` event', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const fullPath = path.resolve('test/fixtures/ok'); const toSend = await getStream(fs.createReadStream(fullPath)); const ifStream = fs.createReadStream(fullPath); await pEvent(ifStream, 'ready'); const body = await got.post({ body: ifStream, }).text(); t.is(toSend, body); }); test('body - sends spec-compliant FormData', withServer, async (t, server, got) => { server.post('/', echoMultipartBody); const form = new FormDataNode(); form.set('a', 'b'); const body = await got.post({body: form}).json<{a: string}>(); t.is(body.a, 'b'); }); test('body - sends files with spec-compliant FormData', withServer, async (t, server, got) => { server.post('/', echoMultipartBody); const fullPath = path.resolve('test/fixtures/ok'); const blobContent = 'Blob content'; const fileContent = 'File content'; const anotherFileContent = await fsPromises.readFile(fullPath, 'utf8'); const expected = { blob: blobContent, file: fileContent, anotherFile: anotherFileContent, }; const form = new FormDataNode(); form.set('blob', new Blob([blobContent])); form.set('file', new File([fileContent], 'file.txt', {type: 'text/plain'})); form.set('anotherFile', await fileFromPath(fullPath, {type: 'text/plain'})); const body = await got.post({body: form}).json<typeof expected>(); t.deepEqual(body, expected); }); test('body - sends form-data with without known length', withServer, async (t, server, got) => { server.post('/', echoMultipartBody); const fullPath = path.resolve('test/fixtures/ok'); function getFileStream() { const fileStream = fs.createReadStream(fullPath); const passThrough = new stream.PassThrough(); fileStream.pipe(passThrough); return passThrough; } const expected = { file: await fsPromises.readFile(fullPath, 'utf8'), }; const form = new FormDataNode(); form.set('file', { [Symbol.toStringTag]: 'File', type: 'text/plain', name: 'file.txt', stream() { return getFileStream(); }, }); const body = await got.post({body: form}).json<typeof expected>(); t.deepEqual(body, expected); }); test('throws on upload error', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const body = new stream.PassThrough(); const message = 'oh no'; await t.throwsAsync(getStream(got.stream.post({ body, hooks: { beforeRequest: [ () => { process.nextTick(() => { body.destroy(new Error(message)); }); }, ], }, })), { instanceOf: UploadError, message, code: 'ERR_UPLOAD', }); }); test('formdata retry', withServer, async (t, server, got) => { server.post('/', echoHeaders); const instance = got.extend({ hooks: { afterResponse: [ async (_response, retryWithMergedOptions) => retryWithMergedOptions({ headers: { foo: 'bar', }, }), ], }, }); const form = new FormData(); form.append('hello', 'world'); await t.throwsAsync(instance.post({ body: form, headers: form.getHeaders(), }).json<{foo?: string}>(), { message: 'Cannot retry with consumed body stream', }); });