thenavisapp
Version:
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
671 lines (543 loc) • 16.2 kB
text/typescript
import {parse} from 'node:url';
import test from 'ava';
import type {Handler} from 'express';
import {pEvent} from 'p-event';
import got, {Options, RequestError, type StrictOptions} from '../source/index.js';
import withServer, {withBodyParsingServer} from './helpers/with-server.js';
import invalidUrl from './helpers/invalid-url.js';
const echoUrl: Handler = (request, response) => {
response.end(request.url);
};
test('`url` is required', async t => {
await t.throwsAsync(
// @ts-expect-error No argument on purpose.
got(),
{
instanceOf: RequestError,
message: 'Missing `url` property',
},
);
const firstError = await t.throwsAsync(got(''));
invalidUrl(t, firstError!, '');
const secondError = await t.throwsAsync(got({url: ''}));
invalidUrl(t, secondError!, '');
});
test('throws if no arguments provided', async t => {
// @ts-expect-error Error tests
await t.throwsAsync(got(), {
instanceOf: RequestError,
message: 'Missing `url` property',
});
});
test('throws if the url option is missing', async t => {
await t.throwsAsync(got({}), {
instanceOf: RequestError,
message: 'Missing `url` property',
});
});
test('throws an error if the protocol is not specified', async t => {
const error = await t.throwsAsync(got('example.com'));
invalidUrl(t, error!, 'example.com');
});
test('properly encodes query string', withServer, async (t, server, got) => {
server.get('/', echoUrl);
const path = '?test=http://example.com?foo=bar';
const {body} = await got(path);
t.is(body, '/?test=http://example.com?foo=bar');
});
test('options are optional', withServer, async (t, server, got) => {
server.get('/test', echoUrl);
t.is((await got('test')).body, '/test');
});
test('methods are normalized', withServer, async (t, server, got) => {
server.post('/test', echoUrl);
const instance = got.extend({
handlers: [
(options, next) => {
if (options.method === options.method.toUpperCase()) {
t.pass();
} else {
t.fail();
}
return next(options);
},
],
});
await instance('test', {method: 'post'});
});
test('throws an error when legacy URL is passed', withServer, async (t, server) => {
server.get('/test', echoUrl);
await t.throwsAsync(
// @ts-expect-error Error tests
got(parse(`${server.url}/test`)),
{
instanceOf: RequestError,
message: 'Expected values which are `string`, `URL`, or `undefined`. Received values of type `Object`.',
},
);
await t.throwsAsync(
got({
protocol: 'http:',
hostname: 'localhost',
port: server.port,
} as any),
{
instanceOf: RequestError,
message: 'Unexpected option: protocol',
},
);
});
test('overrides `searchParams` from options', withServer, async (t, server, got) => {
server.get('/', echoUrl);
const {body} = await got(
'?drop=this',
{
searchParams: {
test: 'wow',
},
},
);
t.is(body, '/?test=wow');
});
test('does not duplicate `searchParams`', withServer, async (t, server, got) => {
server.get('/', echoUrl);
const instance = got.extend({
searchParams: new URLSearchParams({foo: '123'}),
});
const body = await instance('?bar=456').text();
t.is(body, '/?foo=123');
});
test('escapes `searchParams` parameter values', withServer, async (t, server, got) => {
server.get('/', echoUrl);
const {body} = await got({
searchParams: {
test: 'it’s ok',
},
});
t.is(body, '/?test=it%E2%80%99s+ok');
});
test('the `searchParams` option can be a URLSearchParams', withServer, async (t, server, got) => {
server.get('/', echoUrl);
// eslint-disable-next-line unicorn/prevent-abbreviations
const searchParams = new URLSearchParams({test: 'wow'});
const {body} = await got({searchParams});
t.is(body, '/?test=wow');
});
test('ignores empty searchParams object', withServer, async (t, server, got) => {
server.get('/test', echoUrl);
t.is((await got('test', {searchParams: {}})).requestUrl.toString(), `${server.url}/test`);
});
test('throws when passing body with a non payload method', async t => {
await t.throwsAsync(got('https://example.com', {body: 'asdf'}), {
instanceOf: RequestError,
message: 'The `GET` method cannot be used with a body',
});
});
test('`allowGetBody` option', withServer, async (t, server, got) => {
server.get('/test', echoUrl);
await t.notThrowsAsync(got('test', {body: 'asdf', allowGetBody: true}));
});
test('WHATWG URL support', withServer, async (t, server) => {
server.get('/test', echoUrl);
const url = new URL(`${server.url}/test`);
await t.notThrowsAsync(got(url));
});
test('returns streams when using `isStream` option', withServer, async (t, server, got) => {
server.get('/stream', (_request, response) => {
response.end('ok');
});
const data = await pEvent(got('stream', {isStream: true}), 'data');
t.is(data.toString(), 'ok');
});
test('accepts `url` as an option', withServer, async (t, server, got) => {
server.get('/test', echoUrl);
await t.notThrowsAsync(got({url: 'test'}));
});
test('can omit `url` option if using `prefixUrl`', withServer, async (t, server, got) => {
server.get('/', echoUrl);
await t.notThrowsAsync(got({}));
});
test('throws when `options.hooks` is not an object', async t => {
await t.throwsAsync(
// @ts-expect-error Error tests
got('https://example.com', {hooks: 'not object'}),
{
instanceOf: RequestError,
message: 'Expected value which is `Object`, received value of type `string`.',
},
);
});
test('throws when known `options.hooks` value is not an array', async t => {
await t.throwsAsync(
// @ts-expect-error Error tests
got('https://example.com', {hooks: {beforeRequest: {}}}),
{
instanceOf: RequestError,
message: 'Expected values which are `Array` or `undefined`. Received values of type `Object`.',
},
);
});
test('throws when known `options.hooks` array item is not a function', async t => {
await t.throwsAsync(
// @ts-expect-error Error tests
got('https://example.com', {hooks: {beforeRequest: [{}]}}),
{
instanceOf: RequestError,
message: 'Expected value which is `Function`, received value of type `Object`.',
},
);
});
test('does not allow extra keys in `options.hooks`', withServer, async (t, server, got) => {
server.get('/test', echoUrl);
// @ts-expect-error Error tests
await t.throwsAsync(got('test', {hooks: {extra: []}}), {
instanceOf: RequestError,
message: 'Unexpected hook event: extra',
});
});
test('`prefixUrl` option works', withServer, async (t, server, got) => {
server.get('/test/foobar', echoUrl);
const instanceA = got.extend({prefixUrl: `${server.url}/test`});
const {body} = await instanceA('foobar');
t.is(body, '/test/foobar');
});
test('accepts WHATWG URL as the `prefixUrl` option', withServer, async (t, server, got) => {
server.get('/test/foobar', echoUrl);
const instanceA = got.extend({prefixUrl: new URL(`${server.url}/test`)});
const {body} = await instanceA('foobar');
t.is(body, '/test/foobar');
});
test('backslash in the end of `prefixUrl` option is optional', withServer, async (t, server) => {
server.get('/test/foobar', echoUrl);
const instanceA = got.extend({prefixUrl: `${server.url}/test/`});
const {body} = await instanceA('foobar');
t.is(body, '/test/foobar');
});
test('`prefixUrl` can be changed if the URL contains the old one', withServer, async (t, server) => {
server.get('/', echoUrl);
const instanceA = got.extend({
prefixUrl: `${server.url}/meh`,
handlers: [
(options, next) => {
options.prefixUrl = server.url;
return next(options);
},
],
});
const {body} = await instanceA('');
t.is(body, '/');
});
test('throws if the `searchParams` value is invalid', async t => {
await t.throwsAsync(got('https://example.com', {
searchParams: {
// @ts-expect-error Error tests
foo: [],
},
}),
{
instanceOf: RequestError,
message: 'Expected values which are `string`, `number`, `boolean`, `null`, or `undefined`. Received values of type `Array`.',
});
});
test.failing('`context` option is enumerable', withServer, async (t, server, got) => {
server.get('/', echoUrl);
const context = {
foo: 'bar',
};
await got({
context,
hooks: {
beforeRequest: [
options => {
t.deepEqual(options.context, context);
t.true(Object.prototype.propertyIsEnumerable.call(options, 'context'));
},
],
},
});
});
test('`context` option is never frozen', t => {
const client = got.extend({
context: {
token: 'foobar',
},
});
client.defaults.options.context.token = '1234';
t.is(client.defaults.options.context.token, '1234');
});
test('`context` option is accessible when using hooks', withServer, async (t, server) => {
server.get('/', echoUrl);
const context = {
foo: 'bar',
};
await got(server.url, {
context,
hooks: {
beforeRequest: [
options => {
t.deepEqual(options.context, context);
t.false(Object.prototype.propertyIsEnumerable.call(options, 'context'));
},
],
},
});
});
test('`context` option is accessible when extending instances', t => {
const context = {
foo: 'bar',
};
const instance = got.extend({context});
t.deepEqual(instance.defaults.options.context, context);
t.false(Object.prototype.propertyIsEnumerable.call(instance.defaults.options, 'context'));
});
test('`context` option is shallow merged', t => {
const context = {
foo: 'bar',
};
const context2 = {
bar: 'baz',
};
const instance1 = got.extend({context});
t.deepEqual(instance1.defaults.options.context, context);
t.false(Object.prototype.propertyIsEnumerable.call(instance1.defaults.options, 'context'));
const instance2 = instance1.extend({context: context2});
t.deepEqual(instance2.defaults.options.context, {...context, ...context2});
});
test('throws if `options.encoding` is `null`', async t => {
await t.throwsAsync(got('https://example.com', {
// @ts-expect-error For testing purposes
encoding: null,
}), {
instanceOf: RequestError,
message: 'To get a Buffer, set `options.responseType` to `buffer` instead',
});
});
test('`url` option and input argument are mutually exclusive', async t => {
await t.throwsAsync(got('https://example.com', {
url: 'https://example.com',
}), {
instanceOf: RequestError,
message: 'The `url` option is mutually exclusive with the `input` argument',
});
});
test('throws a helpful error when passing `followRedirects`', async t => {
await t.throwsAsync(got('https://example.com', {
// @ts-expect-error For testing purposes
followRedirects: true,
}), {
instanceOf: RequestError,
message: 'The `followRedirects` option does not exist. Use `followRedirect` instead.',
});
});
test('merges `searchParams` instances', t => {
const instance = got.extend({
searchParams: new URLSearchParams('a=1'),
}, {
searchParams: new URLSearchParams('b=2'),
});
// eslint-disable-next-line unicorn/prevent-abbreviations
const searchParams = instance.defaults.options.searchParams as URLSearchParams;
t.is(searchParams.get('a'), '1');
t.is(searchParams.get('b'), '2');
});
test('throws a helpful error when passing `auth`', async t => {
await t.throwsAsync(got('https://example.com', {
// @ts-expect-error For testing purposes
auth: 'username:password',
}), {
instanceOf: RequestError,
message: 'Parameter `auth` is deprecated. Use `username` / `password` instead.',
});
});
test('throws on leading slashes', async t => {
await t.throwsAsync(got('/asdf', {prefixUrl: 'https://example.com'}), {
instanceOf: RequestError,
message: '`url` must not start with a slash',
});
});
test('throws on invalid `dnsCache` option', async t => {
await t.throwsAsync(got('https://example.com', {
// @ts-expect-error Error tests
dnsCache: 123,
}),
{
instanceOf: RequestError,
message: 'Expected values which are `Object`, `boolean`, or `undefined`. Received values of type `number`.',
});
});
test('throws on invalid `agent` option', async t => {
await t.throwsAsync(got('https://example.com', {
agent: {
// @ts-expect-error Error tests
asdf: 123,
},
}), {
instanceOf: RequestError,
message: 'Unexpected agent option: asdf',
});
});
test('fallbacks to native http if `request(...)` returns undefined', withServer, async (t, server, got) => {
server.get('/', echoUrl);
const {body} = await got('', {request: () => undefined});
t.is(body, '/');
});
test('strict options', withServer, async (t, server, got) => {
server.get('/', echoUrl);
const options: StrictOptions = {};
const {body} = await got(options);
t.is(body, '/');
});
test('does not throw on frozen options', withServer, async (t, server, got) => {
server.get('/', echoUrl);
const options: StrictOptions = {};
Object.freeze(options);
const {body} = await got(options);
t.is(body, '/');
});
test('encodes query string included in input', t => {
const {url} = new Options({
url: new URL('https://example.com/?a=b c'),
});
t.is(url!.search, '?a=b%20c');
});
test('normalizes search params included in options', t => {
const {url} = new Options({
url: new URL('https://example.com'),
searchParams: 'a=b c',
});
t.is(url!.search, '?a=b+c');
});
test('reuse options while using init hook', withServer, async (t, server, got) => {
t.plan(2);
server.get('/', echoUrl);
const options = {
hooks: {
init: [
() => {
t.pass();
},
],
},
};
await got('', options);
await got('', options);
});
test('allowGetBody sends json payload', withBodyParsingServer, async (t, server, got) => {
server.get('/', (request, response) => {
if (request.body.hello !== 'world') {
response.statusCode = 400;
}
response.end();
});
const {statusCode} = await got({
allowGetBody: true,
json: {hello: 'world'},
retry: {
limit: 0,
},
throwHttpErrors: false,
});
t.is(statusCode, 200);
});
test('no URL pollution', withServer, async (t, server) => {
server.get('/ok', echoUrl);
const url = new URL(server.url);
const {body} = await got(url, {
hooks: {
beforeRequest: [
options => {
(options.url as URL).pathname = '/ok';
},
],
},
});
t.is(url.pathname, '/');
t.is(body, '/ok');
});
test('prefixUrl is properly replaced when extending', withServer, async (t, server) => {
server.get('/', (request, response) => {
response.end(request.url);
});
server.get('/other/path/', (request, response) => {
response.end(request.url);
});
const parent = got.extend({prefixUrl: server.url});
const child = parent.extend({prefixUrl: `${server.url}/other/path/`});
t.is(await child.get('').text(), '/other/path/');
});
test('throws on too large noise', t => {
/* eslint-disable no-new */
t.throws(() => {
new Options({
retry: {
noise: 101,
},
});
}, {
instanceOf: Error,
message: 'The maximum acceptable retry noise is +/- 100ms, got 101',
});
t.throws(() => {
new Options({
retry: {
noise: -101,
},
});
}, {
instanceOf: Error,
message: 'The maximum acceptable retry noise is +/- 100ms, got -101',
});
t.throws(() => {
new Options({
retry: {
noise: Number.POSITIVE_INFINITY,
},
});
}, {
instanceOf: Error,
message: 'The maximum acceptable retry noise is +/- 100ms, got Infinity',
});
t.throws(() => {
new Options({
retry: {
noise: Number.NEGATIVE_INFINITY,
},
});
}, {
instanceOf: Error,
message: 'The maximum acceptable retry noise is +/- 100ms, got -Infinity',
});
t.notThrows(() => {
new Options({
retry: {
noise: 0,
},
});
});
/* eslint-enable no-new */
});
test('options have url even if some are invalid', async t => {
const error = await t.throwsAsync<RequestError>(got('https://example.com', {
// @ts-expect-error Testing purposes
invalid: true,
}));
t.is((error?.options.url as URL).href, 'https://example.com/');
t.true(error instanceof Error);
});
test('options have url even if some are invalid - got.extend', async t => {
const instance = got.extend({
handlers: [
(options, next) => {
t.is((options.url as URL).href, 'https://example.com/');
return next(options);
},
],
});
await t.throwsAsync(instance('https://example.com', {
// @ts-expect-error Testing purposes
invalid: true,
}),
{
instanceOf: Error,
});
});