diff --git a/package.json b/package.json index 0b9c219d9..a7bdbb894 100644 --- a/package.json +++ b/package.json @@ -118,10 +118,6 @@ "extensions": [ "ts" ], - "rules": { - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/promise-function-async": "off" - }, "ignores": [ "documentation/examples/*" ] diff --git a/readme.md b/readme.md index 8155cb373..dcc949880 100644 --- a/readme.md +++ b/readme.md @@ -1310,7 +1310,7 @@ test('retry function gets iteration count', withServer, async (t, server, got) = retry: { calculateDelay: ({attemptCount}) => { t.true(is.number(attemptCount)); - return attemptCount < 2; + return attemptCount < 2 ? 1 : 0; } } }); diff --git a/source/as-promise.ts b/source/as-promise.ts index 71e6dcf4f..bb521aa4a 100644 --- a/source/as-promise.ts +++ b/source/as-promise.ts @@ -7,7 +7,7 @@ import {normalizeArguments, mergeOptions} from './normalize-arguments'; import requestAsEventEmitter, {proxyEvents} from './request-as-event-emitter'; import {CancelableRequest, GeneralError, NormalizedOptions, Response} from './utils/types'; -const parseBody = (body: Response['body'], responseType: NormalizedOptions['responseType'], statusCode: Response['statusCode']) => { +const parseBody = (body: Response['body'], responseType: NormalizedOptions['responseType'], statusCode: Response['statusCode']): unknown => { if (responseType === 'json' && is.string(body)) { return statusCode === 204 ? '' : JSON.parse(body); } @@ -24,7 +24,7 @@ const parseBody = (body: Response['body'], responseType: NormalizedOptions['resp return body; } - throw new Error(`Failed to parse body of type '${typeof body}' as '${responseType}'`); + throw new Error(`Failed to parse body of type '${typeof body}' as '${responseType!}'`); }; export default function asPromise(options: NormalizedOptions): CancelableRequest { @@ -66,7 +66,7 @@ export default function asPromise(options: NormalizedOptions): CancelableRequ try { for (const [index, hook] of options.hooks.afterResponse.entries()) { - // @ts-ignore + // @ts-ignore Promise is not assignable to CancelableRequest // eslint-disable-next-line no-await-in-loop response = await hook(response, async (updatedOptions: NormalizedOptions) => { updatedOptions = normalizeArguments(mergeOptions(options, { @@ -141,7 +141,7 @@ export default function asPromise(options: NormalizedOptions): CancelableRequ emitter.once('error', reject); proxyEvents(proxy, emitter); - }) as CancelableRequest; + }) as CancelableRequest; promise.on = (name: string, fn: (...args: any[]) => void) => { proxy.on(name, fn); @@ -154,8 +154,7 @@ export default function asPromise(options: NormalizedOptions): CancelableRequ Object.defineProperties(newPromise, Object.getOwnPropertyDescriptors(promise)); - // @ts-ignore The missing properties are added above - return newPromise; + return newPromise as CancelableRequest; }; promise.json = () => { diff --git a/source/as-stream.ts b/source/as-stream.ts index 47afbd5a5..c472e56ac 100644 --- a/source/as-stream.ts +++ b/source/as-stream.ts @@ -1,6 +1,6 @@ import duplexer3 = require('duplexer3'); import stream = require('stream'); -import {IncomingMessage} from 'http'; +import {IncomingMessage, ServerResponse} from 'http'; import {Duplex as DuplexStream, PassThrough as PassThroughStream} from 'stream'; import {HTTPError, ReadError} from './errors'; import requestAsEventEmitter, {proxyEvents} from './request-as-event-emitter'; @@ -14,7 +14,7 @@ export default function asStream(options: NormalizedOptions): ProxyStream const input = new PassThroughStream(); const output = new PassThroughStream(); const proxy = duplexer3(input, output) as ProxyStream; - const piped = new Set(); // TODO: Should be `new Set();`. + const piped = new Set(); let isFinished = false; options.retry.calculateDelay = () => 0; @@ -91,7 +91,7 @@ export default function asStream(options: NormalizedOptions): ProxyStream // It's not possible to decompress already decompressed data, is it? const isAllowed = options.decompress ? key !== 'content-encoding' : true; if (isAllowed) { - destination.setHeader(key, value); + destination.setHeader(key, value!); } } @@ -114,7 +114,7 @@ export default function asStream(options: NormalizedOptions): ProxyStream pipe(destination, options); - if (Reflect.has(destination, 'setHeader')) { + if (destination instanceof ServerResponse) { piped.add(destination); } @@ -122,7 +122,7 @@ export default function asStream(options: NormalizedOptions): ProxyStream }; proxy.unpipe = stream => { - piped.delete(stream); + piped.delete(stream as ServerResponse); return unpipe(stream); }; diff --git a/source/calculate-retry-delay.ts b/source/calculate-retry-delay.ts index 7919b9bde..42ec26984 100644 --- a/source/calculate-retry-delay.ts +++ b/source/calculate-retry-delay.ts @@ -15,7 +15,7 @@ const calculateRetryDelay: RetryFunction = ({attemptCount, retryOptions, error}) const hasMethod = retryOptions.methods.includes(error.options.method); const hasErrorCode = Reflect.has(error, 'code') && retryOptions.errorCodes.includes(error.code); - const hasStatusCode = isErrorWithResponse(error) && Reflect.has(error, 'response') && retryOptions.statusCodes.includes(error.response?.statusCode); + const hasStatusCode = isErrorWithResponse(error) && retryOptions.statusCodes.includes(error.response.statusCode); if (!hasMethod || (!hasErrorCode && !hasStatusCode)) { return 0; } @@ -37,7 +37,7 @@ const calculateRetryDelay: RetryFunction = ({attemptCount, retryOptions, error}) return after; } - if (response?.statusCode === 413) { + if (response.statusCode === 413) { return 0; } } diff --git a/source/create.ts b/source/create.ts index e77012c3a..923a7d5ba 100644 --- a/source/create.ts +++ b/source/create.ts @@ -6,9 +6,10 @@ import {normalizeArguments, mergeOptions} from './normalize-arguments'; import deepFreeze from './utils/deep-freeze'; import { CancelableRequest, + Defaults, + DefaultOptions, ExtendOptions, HandlerFunction, - NormalizedDefaults, NormalizedOptions, Options, Response, @@ -28,7 +29,7 @@ export type GotReturn = CancelableRequest | ProxyStream; const getPromiseOrStream = (options: NormalizedOptions): GotReturn => options.isStream ? asStream(options) : asPromise(options); -const isGotInstance = (value: any): value is Got => ( +const isGotInstance = (value: Got | ExtendOptions): value is Got => ( Reflect.has(value, 'defaults') && Reflect.has(value.defaults, 'options') ); @@ -55,7 +56,7 @@ interface GotFunctions { export interface Got extends Record, GotFunctions { stream: GotStream; - defaults: NormalizedDefaults | Readonly; + defaults: Defaults | Readonly; GotError: typeof errors.GotError; CacheError: typeof errors.CacheError; RequestError: typeof errors.RequestError; @@ -87,12 +88,13 @@ const aliases: readonly HTTPAlias[] = [ export const defaultHandler: HandlerFunction = (options, next) => next(options); -const create = (defaults: NormalizedDefaults & {_rawHandlers?: HandlerFunction[]}): Got => { +const create = (defaults: Defaults): Got => { // Proxy properties from next handlers + // @ts-ignore Internal use only. defaults._rawHandlers = defaults.handlers; defaults.handlers = defaults.handlers.map(fn => ((options, next) => { // This will be assigned by assigning result - let root!: GotReturn; + let root!: ReturnType; const result = fn(options, newOptions => { root = next(newOptions); @@ -105,17 +107,17 @@ const create = (defaults: NormalizedDefaults & {_rawHandlers?: HandlerFunction[] } return result; - }) as HandlerFunction); + })); // @ts-ignore Because the for loop handles it for us, as well as the other Object.defines const got: Got = (url: URLOrOptions, options?: Options): GotReturn => { let iteration = 0; - const iterateHandlers = (newOptions: Parameters[0]): ReturnType => { + const iterateHandlers = (newOptions: NormalizedOptions): GotReturn => { return defaults.handlers[iteration++]( newOptions, // @ts-ignore TS doesn't know that it calls `getPromiseOrStream` at the end iteration === defaults.handlers.length ? getPromiseOrStream : iterateHandlers - ); + ) as GotReturn; }; /* eslint-disable @typescript-eslint/return-await */ @@ -134,13 +136,15 @@ const create = (defaults: NormalizedDefaults & {_rawHandlers?: HandlerFunction[] got.extend = (...instancesOrOptions) => { const optionsArray: Options[] = [defaults.options]; - let handlers: HandlerFunction[] = [...defaults._rawHandlers!]; + // @ts-ignore Internal use only. + let handlers: HandlerFunction[] = [...defaults._rawHandlers]; let mutableDefaults: boolean | undefined; for (const value of instancesOrOptions) { if (isGotInstance(value)) { optionsArray.push(value.defaults.options); - handlers.push(...value.defaults._rawHandlers!); + // @ts-ignore Internal use only. + handlers.push(...value.defaults._rawHandlers); mutableDefaults = value.defaults.mutableDefaults; } else { optionsArray.push(value); @@ -160,7 +164,7 @@ const create = (defaults: NormalizedDefaults & {_rawHandlers?: HandlerFunction[] } return create({ - options: mergeOptions(...optionsArray), + options: mergeOptions(...optionsArray) as DefaultOptions, handlers, mutableDefaults: Boolean(mutableDefaults) }); @@ -170,7 +174,7 @@ const create = (defaults: NormalizedDefaults & {_rawHandlers?: HandlerFunction[] got.stream = (url, options) => got(url, {...options, isStream: true}); for (const method of aliases) { - // @ts-ignore + // @ts-ignore GotReturn does not equal GotReturn got[method] = (url: URLOrOptions, options?: Options): GotReturn => got(url, {...options, method}); got.stream[method] = (url, options) => got.stream(url, {...options, method}); } diff --git a/source/errors.ts b/source/errors.ts index b40df8d68..ae6c24d79 100644 --- a/source/errors.ts +++ b/source/errors.ts @@ -79,7 +79,7 @@ export class HTTPError extends GotError { declare readonly response: Response; constructor(response: Response, options: NormalizedOptions) { - super(`Response code ${response.statusCode} (${response.statusMessage ?? ''})`, {}, options); + super(`Response code ${response.statusCode} (${response.statusMessage!})`, {}, options); this.name = 'HTTPError'; Object.defineProperty(this, 'response', { diff --git a/source/get-response.ts b/source/get-response.ts index 851702922..ff52b0cf3 100644 --- a/source/get-response.ts +++ b/source/get-response.ts @@ -9,7 +9,7 @@ import {NormalizedOptions} from './utils/types'; const pipeline = promisify(stream.pipeline); -export default async (response: IncomingMessage, options: NormalizedOptions, emitter: EventEmitter) => { +export default async (response: IncomingMessage, options: NormalizedOptions, emitter: EventEmitter): Promise => { const downloadBodySize = Number(response.headers['content-length']) || undefined; const progressStream = createProgressStream('downloadProgress', emitter, downloadBodySize); diff --git a/source/index.ts b/source/index.ts index 577458a76..13090e324 100644 --- a/source/index.ts +++ b/source/index.ts @@ -59,7 +59,9 @@ const defaults: Defaults = { resolveBodyOnly: false, maxRedirects: 10, prefixUrl: '', - methodRewriting: true + methodRewriting: true, + ignoreInvalidCookies: false, + context: {} }, handlers: [defaultHandler], mutableDefaults: false diff --git a/source/normalize-arguments.ts b/source/normalize-arguments.ts index 4ed08954b..bf39db4fd 100644 --- a/source/normalize-arguments.ts +++ b/source/normalize-arguments.ts @@ -4,6 +4,7 @@ import http = require('http'); import https = require('https'); import Keyv = require('keyv'); import lowercaseKeys = require('lowercase-keys'); +import stream = require('stream'); import toReadableStream = require('to-readable-stream'); import is from '@sindresorhus/is'; import CacheableLookup from 'cacheable-lookup'; @@ -22,6 +23,7 @@ import { Method, NormalizedOptions, Options, + RequestFunction, URLOrOptions } from './utils/types'; @@ -39,6 +41,7 @@ const nonEnumerableProperties: NonEnumerableProperty[] = [ const isAgentByProtocol = (agent: Options['agent']): agent is AgentByProtocol => is.object(agent); +// TODO: `preNormalizeArguments` should merge `options` & `defaults` export const preNormalizeArguments = (options: Options, defaults?: NormalizedOptions): NormalizedOptions => { // `options.headers` if (is.undefined(options.headers)) { @@ -47,6 +50,12 @@ export const preNormalizeArguments = (options: Options, defaults?: NormalizedOpt options.headers = lowercaseKeys(options.headers); } + for (const [key, value] of Object.entries(options.headers)) { + if (is.null_(value)) { + throw new TypeError(`Use \`undefined\` instead of \`null\` to delete the \`${key}\` header`); + } + } + // `options.prefixUrl` if (is.urlInstance(options.prefixUrl) || is.string(options.prefixUrl)) { options.prefixUrl = options.prefixUrl.toString(); @@ -79,11 +88,13 @@ export const preNormalizeArguments = (options: Options, defaults?: NormalizedOpt if (defaults) { for (const event of knownHookEvents) { - // @ts-ignore TS is dumb. - options.hooks[event] = [ - ...defaults.hooks[event], - ...options.hooks[event]! - ]; + if (!(Reflect.has(options.hooks, event) && is.undefined(options.hooks[event]))) { + // @ts-ignore Union type array is not assignable to union array type + options.hooks[event] = [ + ...defaults.hooks[event], + ...options.hooks[event]! + ]; + } } } @@ -120,7 +131,7 @@ export const preNormalizeArguments = (options: Options, defaults?: NormalizedOpt } if (options.retry.maxRetryAfter === undefined) { - // @ts-ignore We assign if it is undefined, so this is correct + // @ts-ignore We assign if it is undefined, so this IS correct options.retry.maxRetryAfter = Math.min( ...[options.timeout.request, options.timeout.connect].filter((n): n is number => !is.nullOrUndefined(n)) ); @@ -158,7 +169,7 @@ export const preNormalizeArguments = (options: Options, defaults?: NormalizedOpt // Horrible `tough-cookie` check if (setCookie.length === 4 && getCookieString.length === 0) { if (!Reflect.has(setCookie, promisify.custom)) { - // @ts-ignore TS is dumb. + // @ts-ignore We check for non-promisified setCookie, so this IS correct setCookie = promisify(setCookie.bind(options.cookieJar)); getCookieString = promisify(getCookieString.bind(options.cookieJar)); } @@ -176,6 +187,11 @@ export const preNormalizeArguments = (options: Options, defaults?: NormalizedOpt throw new TypeError('To get a Buffer, set `options.responseType` to `buffer` instead'); } + // `options.maxRedirects` + if (!Reflect.has(options, 'maxRedirects') && !(defaults && Reflect.has(defaults, 'maxRedirects'))) { + options.maxRedirects = 0; + } + return options as NormalizedOptions; }; @@ -186,10 +202,6 @@ export const mergeOptions = (...sources: Options[]): NormalizedOptions => { const properties: Partial<{[Key in NonEnumerableProperty]: any}> = {}; for (const source of sources) { - if (!source) { - continue; - } - merge(mergedOptions, preNormalizeArguments(merge({}, source), mergedOptions)); for (const name of nonEnumerableProperties) { @@ -224,13 +236,13 @@ export const normalizeArguments = (url: URLOrOptions, options?: Options, default if (is.urlInstance(url) || is.string(url)) { options.url = url; - options = mergeOptions((defaults?.options) ?? {}, options); + options = mergeOptions(defaults?.options ?? {}, options); } else { if (Reflect.has(url, 'resolve')) { throw new Error('The legacy `url.Url` is deprecated. Use `URL` instead.'); } - options = mergeOptions((defaults?.options) ?? {}, url, options); + options = mergeOptions(defaults?.options ?? {}, url, options); } // Normalize URL @@ -272,18 +284,15 @@ export const normalizeArguments = (url: URLOrOptions, options?: Options, default if (is.undefined(value)) { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete normalizedOptions.headers[key]; - } else if (is.null_(value)) { - throw new TypeError('Use `undefined` instead of `null` to delete HTTP headers'); } } for (const hook of normalizedOptions.hooks.init) { - if (is.asyncFunction(hook)) { + const result = hook(normalizedOptions); + + if (is.promise(result)) { throw new TypeError('The `init` hook must be a synchronous function'); } - - // @ts-ignore TS is dumb. - hook(normalizedOptions); } return normalizedOptions; @@ -292,7 +301,8 @@ export const normalizeArguments = (url: URLOrOptions, options?: Options, default const withoutBody: ReadonlySet = new Set(['GET', 'HEAD']); export type NormalizedRequestArguments = Merge; }>; @@ -301,41 +311,49 @@ export const normalizeRequestArguments = async (options: NormalizedOptions): Pro // Serialize body const {headers} = options; - const isForm = !is.undefined(options.form); - const isJSON = !is.undefined(options.json); - const isBody = !is.undefined(options.body); - if ((isBody || isForm || isJSON) && withoutBody.has(options.method)) { - throw new TypeError(`The \`${options.method}\` method cannot be used with a body`); - } + const noContentType = is.undefined(headers['content-type']); + + { + // TODO: these checks should be moved to `preNormalizeArguments` + const isForm = !is.undefined(options.form); + const isJSON = !is.undefined(options.json); + const isBody = !is.undefined(options.body); + if ((isBody || isForm || isJSON) && withoutBody.has(options.method)) { + throw new TypeError(`The \`${options.method}\` method cannot be used with a body`); + } - if ([isBody, isForm, isJSON].filter(isTrue => isTrue).length > 1) { - throw new TypeError('The `body`, `json` and `form` options are mutually exclusive'); - } + if ([isBody, isForm, isJSON].filter(isTrue => isTrue).length > 1) { + throw new TypeError('The `body`, `json` and `form` options are mutually exclusive'); + } - if (isBody) { - if (is.object(options.body) && isFormData(options.body)) { - // Special case for https://github.com/form-data/form-data - if (!Reflect.has(headers, 'content-type')) { - // @ts-ignore TS is dumb. - headers['content-type'] = `multipart/form-data; boundary=${options.body.getBoundary()}`; - } - } else if (!is.nodeStream(options.body) && !is.string(options.body) && !is.buffer(options.body)) { + if ( + isBody && + !is.nodeStream(options.body) && + !is.string(options.body) && + !is.buffer(options.body) && + !(is.object(options.body) && isFormData(options.body)) + ) { throw new TypeError('The `body` option must be a stream.Readable, string or Buffer'); } - } else if (isForm) { - if (!is.object(options.form)) { + + if (isForm && !is.object(options.form)) { throw new TypeError('The `form` option must be an Object'); } + } - if (!Reflect.has(headers, 'content-type')) { - // @ts-ignore TS is dumb. + if (options.body) { + // Special case for https://github.com/form-data/form-data + if (is.object(options.body) && isFormData(options.body) && noContentType) { + headers['content-type'] = `multipart/form-data; boundary=${options.body.getBoundary()}`; + } + } else if (options.form) { + if (noContentType) { headers['content-type'] = 'application/x-www-form-urlencoded'; } options.body = (new URLSearchParams(options.form as Record)).toString(); - } else if (isJSON) { - if (!Reflect.has(headers, 'content-type')) { - // @ts-ignore TS is dumb. + } else if (options.json) { + if (noContentType) { headers['content-type'] = 'application/json'; } @@ -357,12 +375,12 @@ export const normalizeRequestArguments = async (options: NormalizedOptions): Pro // Content-Length header field when the request message does not contain // a payload body and the method semantics do not anticipate such a // body. - if (!Reflect.has(headers, 'content-length') && !Reflect.has(headers, 'transfer-encoding')) { + if (noContentType && is.undefined(headers['transfer-encoding'])) { if ( (options.method === 'POST' || options.method === 'PUT' || options.method === 'PATCH') && !is.undefined(uploadBodySize) ) { - // @ts-ignore TS is dumb. + // @ts-ignore We assign if it is undefined, so this IS correct headers['content-length'] = String(uploadBodySize); } } @@ -394,14 +412,12 @@ export const normalizeRequestArguments = async (options: NormalizedOptions): Pro if (matches?.groups) { const {socketPath, path} = matches.groups; - // It's a bug! - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions options = { ...options, socketPath, path, host: '' - } as NormalizedOptions; + }; } } diff --git a/source/request-as-event-emitter.ts b/source/request-as-event-emitter.ts index c2b76ed15..76fa850d6 100644 --- a/source/request-as-event-emitter.ts +++ b/source/request-as-event-emitter.ts @@ -15,7 +15,7 @@ import timedOut, {TimeoutError as TimedOutTimeoutError} from './utils/timed-out' import {GeneralError, NormalizedOptions, Response, ResponseObject} from './utils/types'; import urlToOptions from './utils/url-to-options'; -const setImmediateAsync = () => new Promise(resolve => setImmediate(resolve)); +const setImmediateAsync = async (): Promise => new Promise(resolve => setImmediate(resolve)); const pipeline = promisify(stream.pipeline); const redirectCodes: ReadonlySet = new Set([300, 301, 302, 303, 304, 307, 308]); @@ -25,7 +25,7 @@ export interface RequestAsEventEmitter extends EventEmitter { abort: () => void; } -export default (options: NormalizedOptions) => { +export default (options: NormalizedOptions): RequestAsEventEmitter => { const emitter = new EventEmitter() as RequestAsEventEmitter; const requestURL = options.url.toString(); @@ -84,10 +84,10 @@ export default (options: NormalizedOptions) => { const rawCookies = typedResponse.headers['set-cookie']; if (Reflect.has(options, 'cookieJar') && rawCookies) { - let promises: Array> = rawCookies.map((rawCookie: string) => options.cookieJar.setCookie(rawCookie, typedResponse.url)); + let promises: Array> = rawCookies.map(async (rawCookie: string) => options.cookieJar.setCookie(rawCookie, typedResponse.url)); if (options.ignoreInvalidCookies) { - promises = promises.map(p => p.catch(() => {})); + promises = promises.map(async p => p.catch(() => {})); } await Promise.all(promises); @@ -143,7 +143,7 @@ export default (options: NormalizedOptions) => { const handleRequest = async (request: http.ClientRequest): Promise => { // `request.aborted` is a boolean since v11.0.0: https://github.com/nodejs/node/commit/4b00c4fafaa2ae8c41c1f78823c0feb810ae4723#diff-e3bc37430eb078ccbafe3aa3b570c91a - const isAborted = () => typeof request.aborted === 'number' || (request.aborted as unknown as boolean); + const isAborted = (): boolean => typeof request.aborted === 'number' || (request.aborted as unknown as boolean); currentRequest = request; @@ -159,7 +159,7 @@ export default (options: NormalizedOptions) => { } }; - const attachErrorHandler = () => { + const attachErrorHandler = (): void => { request.once('error', error => { // We need to allow `TimedOutTimeoutError` here, because `stream.pipeline(…)` aborts the request automatically. if (isAborted() && !(error instanceof TimedOutTimeoutError)) { @@ -179,8 +179,7 @@ export default (options: NormalizedOptions) => { const uploadStream = createProgressStream('uploadProgress', emitter, httpOptions.headers!['content-length'] as string); await pipeline( - // @ts-ignore Cannot assign ReadableStream to ReadableStream - httpOptions.body, + httpOptions.body!, uploadStream, request ); @@ -223,8 +222,7 @@ export default (options: NormalizedOptions) => { } else { // Catches errors thrown by calling `requestFn(…)` try { - // @ts-ignore 1. TS complains that URLSearchParams is not the same as URLSearchParams. - // 2. It doesn't notice that `options.timeout` is deleted above. + // @ts-ignore URLSearchParams does not equal URLSearchParams handleRequest(httpOptions.request(options.url, httpOptions, handleResponse)); } catch (error) { emitError(new RequestError(error, options)); @@ -307,7 +305,7 @@ export default (options: NormalizedOptions) => { return emitter; }; -export const proxyEvents = (proxy: EventEmitter | ProxyStream, emitter: RequestAsEventEmitter) => { +export const proxyEvents = (proxy: EventEmitter | ProxyStream, emitter: RequestAsEventEmitter): void => { const events = [ 'request', 'redirect', diff --git a/source/types/url/index.d.ts b/source/types/url/index.d.ts new file mode 100644 index 000000000..e4a92c82b --- /dev/null +++ b/source/types/url/index.d.ts @@ -0,0 +1,38 @@ +// TODO: Remove this when https://github.com/DefinitelyTyped/DefinitelyTyped/issues/34960 is resolved +declare global { + class URL { + readonly origin: string; + readonly searchParams: URLSearchParams; + hash: string; + host: string; + hostname: string; + href: string; + password: string; + pathname: string; + port: string; + protocol: string; + search: string; + username: string; + + constructor(input: string, base?: string | URL); + toString(): string; + toJSON(): string; + } + + class URLSearchParams implements Iterable<[string, string]> { + constructor(init?: URLSearchParams | string | {[key: string]: string | string[] | undefined} | Iterable<[string, string]> | Array<[string, string]>); + append(name: string, value: string): void; + delete(name: string): void; + entries(): IterableIterator<[string, string]>; + forEach(callback: (value: string, name: string, searchParams: this) => void): void; + get(name: string): string | null; + getAll(name: string): string[]; + has(name: string): boolean; + keys(): IterableIterator; + set(name: string, value: string): void; + sort(): void; + toString(): string; + values(): IterableIterator; + [Symbol.iterator](): IterableIterator<[string, string]>; + } +} diff --git a/source/utils/merge.ts b/source/utils/merge.ts index 1bd7d320d..38960a339 100644 --- a/source/utils/merge.ts +++ b/source/utils/merge.ts @@ -7,21 +7,21 @@ export default function merge { +export default (request: ClientRequest, delays: Delays, options: TimedOutOptions): () => void => { if (Reflect.has(request, reentry)) { return noop; } diff --git a/source/utils/types.ts b/source/utils/types.ts index b01203338..355e4a337 100644 --- a/source/utils/types.ts +++ b/source/utils/types.ts @@ -5,9 +5,9 @@ import CacheableRequest = require('cacheable-request'); import PCancelable = require('p-cancelable'); import ResponseLike = require('responselike'); import {Readable as ReadableStream} from 'stream'; -import CacheableLookup from 'cacheable-lookup'; import {Timings} from '@szmarczak/http-timer'; -import {Except, Merge} from 'type-fest'; +import CacheableLookup from 'cacheable-lookup'; +import {Except, Merge, Promisable} from 'type-fest'; import {GotReturn} from '../create'; import {GotError, HTTPError, MaxRedirectsError, ParseError} from '../errors'; import {Hooks} from '../known-hook-events'; @@ -86,14 +86,14 @@ export interface RetryObject { export type RetryFunction = (retryObject: RetryObject) => number; -export type HandlerFunction = (options: NormalizedOptions, next: (options: NormalizedOptions) => T) => T; +export type HandlerFunction = (options: NormalizedOptions, next: (options: NormalizedOptions) => T) => Promisable; export interface DefaultRetryOptions { limit: number; methods: Method[]; statusCodes: number[]; errorCodes: string[]; - calculateDelay?: RetryFunction; + calculateDelay: RetryFunction; maxRetryAfter?: number; } @@ -101,7 +101,7 @@ export interface RetryOptions extends Partial { retries?: number; } -export type RequestFunction = typeof https.request; +export type RequestFunction = typeof http.request; export interface AgentByProtocol { http?: http.Agent; @@ -120,46 +120,60 @@ export interface Delays { export type Headers = Record; -interface CookieJar { +interface ToughCookieJar { + getCookieString(currentUrl: string, options: {[key: string]: unknown}, cb: (err: Error | null, cookies: string) => void): void; getCookieString(url: string, callback: (error: Error | null, cookieHeader: string) => void): void; - getCookieString(url: string): Promise; + setCookie(cookieOrString: unknown, currentUrl: string, options: {[key: string]: unknown}, cb: (err: Error | null, cookie: unknown) => void): void; setCookie(rawCookie: string, url: string, callback: (error: Error | null, result: unknown) => void): void; - setCookie(rawCookie: string, url: string): Promise; } -export interface DefaultOptions { - method: Method; - retry: DefaultRetryOptions | number; - timeout: Delays | number; - headers: Headers; - hooks: Hooks; - decompress: boolean; - throwHttpErrors: boolean; - followRedirect: boolean; - isStream: boolean; - cache: CacheableRequest.StorageAdapter | string | false; - dnsCache: CacheableLookup | Map | Keyv | false; - useElectronNet: boolean; - responseType: ResponseType; - resolveBodyOnly: boolean; - maxRedirects: number; - prefixUrl: URL | string; +interface PromiseCookieJar { + getCookieString(url: string): Promise; + setCookie(rawCookie: string, url: string): Promise; } -// The library overrides agent/timeout in a non-standard way, so we have to override them -export interface Options extends Partial>, Merge, URLOptions> { +/* eslint-disable @typescript-eslint/indent */ +export type DefaultOptions = Merge< + Required< + Except< + GotOptions, + // Overrode + 'hooks' | + 'retry' | + 'timeout' | + 'context' | + // Should not be present + 'agent' | + 'body' | + 'cookieJar' | + 'encoding' | + 'form' | + 'json' | + 'lookup' | + 'request' | + 'url' + > + >, + { + hooks: Required; + retry: DefaultRetryOptions; + timeout: Delays; + context: {[key: string]: any}; + } +>; +/* eslint-enable @typescript-eslint/indent */ + +export interface GotOptions { url?: URL | string; body?: string | Buffer | ReadableStream; - hostname?: string; - socketPath?: string; - hooks?: Partial; + hooks?: Hooks; decompress?: boolean; isStream?: boolean; encoding?: BufferEncoding; method?: Method; retry?: RetryOptions | number; throwHttpErrors?: boolean; - cookieJar?: CookieJar; + cookieJar?: ToughCookieJar | PromiseCookieJar; ignoreInvalidCookies?: boolean; request?: RequestFunction; agent?: http.Agent | https.Agent | boolean | AgentByProtocol; @@ -180,17 +194,22 @@ export interface Options extends Partial>, Merge methodRewriting?: boolean; } -export interface NormalizedOptions extends Except, Except { +export type Options = Merge>; + +export interface NormalizedOptions extends Options { // Normalized Got options headers: Headers; hooks: Required; timeout: Delays; dnsCache?: CacheableLookup | false; + lookup?: CacheableLookup['lookup']; retry: Required; prefixUrl: string; method: Method; url: URL; cacheableRequest?: (options: string | URL | http.RequestOptions, callback?: (response: http.ServerResponse | ResponseLike) => void) => CacheableRequest.Emitter; + cookieJar?: PromiseCookieJar; + maxRedirects: number; // UNIX socket support path?: string; @@ -202,16 +221,10 @@ export interface ExtendOptions extends Options { } export interface Defaults { - options: Merge}>; + options: DefaultOptions; handlers: HandlerFunction[]; mutableDefaults: boolean; -} - -export interface NormalizedDefaults { - options: Merge}>; - handlers: HandlerFunction[]; _rawHandlers?: HandlerFunction[]; - mutableDefaults: boolean; } export type URLOrOptions = Options | string; diff --git a/test/agent.ts b/test/agent.ts index 1cb5025a9..149588c34 100644 --- a/test/agent.ts +++ b/test/agent.ts @@ -1,12 +1,13 @@ -import {TLSSocket} from 'tls'; import {Agent as HttpAgent} from 'http'; import {Agent as HttpsAgent} from 'https'; -import test from 'ava'; +import {Socket} from 'net'; +import {TLSSocket} from 'tls'; +import test, {Constructor} from 'ava'; import sinon = require('sinon'); import {ExtendedTestServer} from './helpers/types'; import withServer from './helpers/with-server'; -const prepareServer = (server: ExtendedTestServer) => { +const prepareServer = (server: ExtendedTestServer): void => { server.get('/', (request, response) => { if (request.socket instanceof TLSSocket) { response.end('https'); @@ -30,9 +31,9 @@ const prepareServer = (server: ExtendedTestServer) => { }); }; -// TODO: Type this stricter if possible -const createAgentSpy = (Cls: T) => { - const agent = new Cls({keepAlive: true}); +const createAgentSpy = (AgentClass: Constructor): {agent: T; spy: sinon.SinonSpy} => { + const agent: T = new AgentClass({keepAlive: true}); + // @ts-ignore This IS correct const spy = sinon.spy(agent, 'addRequest'); return {agent, spy}; }; @@ -75,7 +76,7 @@ test('redirects from http to https work with an agent object', withServer, async prepareServer(server); const {agent: httpAgent, spy: httpSpy} = createAgentSpy(HttpAgent); - const {agent: httpsAgent, spy: httpsSpy} = createAgentSpy(HttpsAgent); + const {agent: httpsAgent, spy: httpsSpy} = createAgentSpy(HttpsAgent); t.truthy((await got('httpToHttps', { rejectUnauthorized: false, @@ -129,7 +130,7 @@ test('socket connect listener cleaned up after request', withServer, async (t, s }); } - for (const value of Object.values(agent.freeSockets) as [any]) { + for (const value of Object.values(agent.freeSockets) as [Socket[]]) { for (const sock of value) { t.is(sock.listenerCount('connect'), 0); } diff --git a/test/arguments.ts b/test/arguments.ts index a34498f8e..a0e8ae4da 100644 --- a/test/arguments.ts +++ b/test/arguments.ts @@ -1,7 +1,6 @@ /* eslint-disable node/no-deprecated-api */ import {parse} from 'url'; import test from 'ava'; -import {StorageAdapter} from 'cacheable-request'; import {Handler} from 'express'; import pEvent = require('p-event'); import got from '../source'; @@ -33,7 +32,10 @@ test('`url` should be utf-8 encoded', async t => { test('throws if no arguments provided', async t => { // @ts-ignore Error tests - await t.throwsAsync(got(), TypeError, 'Missing `url` argument'); + await t.throwsAsync(got(), { + instanceOf: TypeError, + message: 'Missing `url` argument' + }); }); test('throws an error if the protocol is not specified', async t => { @@ -112,8 +114,14 @@ test('overrides `searchParams` from options', withServer, async (t, server, got) }, set(key: string) { t.is(key, `cacheable-request:GET:${server.url}/?test=wow`); + }, + delete() { + return true; + }, + clear() { + return undefined; } - } as unknown as StorageAdapter + } } ); diff --git a/test/cancel.ts b/test/cancel.ts index ef8b1bcc4..1125052c1 100644 --- a/test/cancel.ts +++ b/test/cancel.ts @@ -5,12 +5,12 @@ import test from 'ava'; import pEvent = require('p-event'); import getStream = require('get-stream'); import {Handler} from 'express'; -import got, {CancelError, RetryOptions} from '../source'; +import got, {CancelError} from '../source'; import slowDataStream from './helpers/slow-data-stream'; import {ExtendedTestServer, GlobalClock} from './helpers/types'; import withServer, {withServerAndLolex} from './helpers/with-server'; -const prepareServer = (server: ExtendedTestServer, clock: GlobalClock) => { +const prepareServer = (server: ExtendedTestServer, clock: GlobalClock): {emitter: EventEmitter; promise: Promise} => { const emitter = new EventEmitter(); const promise = new Promise((resolve, reject) => { @@ -62,8 +62,9 @@ test.serial('does not retry after cancelation', withServerAndLolex, async (t, se retry: { calculateDelay: () => { t.fail('Makes a new try after cancelation'); + return 0; } - } as unknown as RetryOptions + } }); emitter.once('sentRedirect', () => { diff --git a/test/cookies.ts b/test/cookies.ts index 41f592b07..fdaecdf87 100644 --- a/test/cookies.ts +++ b/test/cookies.ts @@ -1,10 +1,8 @@ import net = require('net'); -import {AddressInfo} from 'net'; import test from 'ava'; import toughCookie = require('tough-cookie'); import delay = require('delay'); import got from '../source'; -import {OptionsOfDefaultResponseBody} from '../source/create'; import withServer from './helpers/with-server'; test('reads a cookie', withServer, async (t, server, got) => { @@ -15,7 +13,7 @@ test('reads a cookie', withServer, async (t, server, got) => { const cookieJar = new toughCookie.CookieJar(); - await got({cookieJar} as unknown as OptionsOfDefaultResponseBody); + await got({cookieJar}); const cookie = cookieJar.getCookiesSync(server.url)[0]; t.is(cookie.key, 'hello'); @@ -30,7 +28,7 @@ test('reads multiple cookies', withServer, async (t, server, got) => { const cookieJar = new toughCookie.CookieJar(); - await got({cookieJar} as unknown as OptionsOfDefaultResponseBody); + await got({cookieJar}); const cookies = cookieJar.getCookiesSync(server.url); const cookieA = cookies[0]; @@ -56,7 +54,7 @@ test('cookies doesn\'t break on redirects', withServer, async (t, server, got) = const cookieJar = new toughCookie.CookieJar(); - const {body} = await got('redirect', {cookieJar} as unknown as OptionsOfDefaultResponseBody); + const {body} = await got('redirect', {cookieJar}); t.is(body, 'hello=world; foo=bar'); }); @@ -68,7 +66,7 @@ test('throws on invalid cookies', withServer, async (t, server, got) => { const cookieJar = new toughCookie.CookieJar(); - await t.throwsAsync(got({cookieJar} as unknown as OptionsOfDefaultResponseBody), 'Cookie has domain set to a public suffix'); + await t.throwsAsync(got({cookieJar}), 'Cookie has domain set to a public suffix'); }); test('does not throw on invalid cookies when options.ignoreInvalidCookies is set', withServer, async (t, server, got) => { @@ -82,7 +80,7 @@ test('does not throw on invalid cookies when options.ignoreInvalidCookies is set await got({ cookieJar, ignoreInvalidCookies: true - } as unknown as OptionsOfDefaultResponseBody); + }); const cookies = cookieJar.getCookiesSync(server.url); t.is(cookies.length, 0); @@ -93,10 +91,16 @@ test('catches store errors', async t => { const cookieJar = new toughCookie.CookieJar({ findCookies: (_, __, callback) => { callback(new Error(error), []); - } - } as toughCookie.Store); + }, + findCookie: () => {}, + getAllCookies: () => {}, + putCookie: () => {}, + removeCookies: () => {}, + removeCookie: () => {}, + updateCookie: () => {} + }); - await t.throwsAsync(got('https://example.com', {cookieJar} as unknown as OptionsOfDefaultResponseBody), error); + await t.throwsAsync(got('https://example.com', {cookieJar}), error); }); test('overrides options.headers.cookie', withServer, async (t, server, got) => { @@ -117,7 +121,7 @@ test('overrides options.headers.cookie', withServer, async (t, server, got) => { headers: { cookie: 'a=b' } - } as unknown as OptionsOfDefaultResponseBody); + }); t.is(body, 'hello=world; foo=bar'); }); @@ -137,8 +141,7 @@ test('no unhandled errors', async t => { } }; - // @ts-ignore Error tests - await t.throwsAsync(got(`http://127.0.0.1:${(server.address() as AddressInfo).port}`, options), {message}); + await t.throwsAsync(got(`http://127.0.0.1:${(server.address() as net.AddressInfo).port}`, options), {message}); await delay(500); t.pass(); @@ -189,3 +192,21 @@ test('throws on invalid `options.cookieJar.getCookieString`', async t => { } }), '`options.cookieJar.getCookieString` needs to be an async function with 1 argument'); }); + +test('cookies are cleared when redirecting to a different hostname (no cookieJar)', withServer, async (t, server, got) => { + server.get('/', (_request, response) => { + response.writeHead(302, { + location: 'https://httpbin.org/anything' + }); + response.end(); + }); + + const {headers} = await got('', { + headers: { + cookie: 'foo=bar', + 'user-agent': 'custom' + } + }).json(); + t.is(headers.Cookie, undefined); + t.is(headers['User-Agent'], 'custom'); +}); diff --git a/test/create.ts b/test/create.ts index 55f1dc0f0..099f390a7 100644 --- a/test/create.ts +++ b/test/create.ts @@ -1,10 +1,9 @@ -import {Agent as HttpAgent, request as httpRequest} from 'http'; +import {Agent as HttpAgent, IncomingMessage, request as httpRequest, RequestOptions} from 'http'; import test from 'ava'; import is from '@sindresorhus/is'; import {Handler} from 'express'; import got, { BeforeRequestHook, - HandlerFunction, Headers, Hooks, RequestFunction @@ -187,8 +186,13 @@ test('ability to pass a custom request method', withServer, async (t, server, go let called = false; - const request: RequestFunction = (...args: [any, any, any?]) => { + const request: RequestFunction = (...args: [ + string | URL | RequestOptions, + (RequestOptions | ((res: IncomingMessage) => void))?, + ((res: IncomingMessage) => void)? + ]) => { called = true; + // @ts-ignore Overload error return httpRequest(...args); }; @@ -235,13 +239,13 @@ test('extend with custom handlers', withServer, async (t, server, got) => { test('extend with instances', t => { const a = got.extend({prefixUrl: new URL('https://example.com/')}); const b = got.extend(a); - t.is(b.defaults.options.prefixUrl!.toString(), 'https://example.com/'); + t.is(b.defaults.options.prefixUrl.toString(), 'https://example.com/'); }); test('extend with a chain', t => { const a = got.extend({prefixUrl: 'https://example.com/'}); const b = got.extend(a, {headers: {foo: 'bar'}}); - t.is(b.defaults.options.prefixUrl!.toString(), 'https://example.com/'); + t.is(b.defaults.options.prefixUrl.toString(), 'https://example.com/'); t.is(b.defaults.options.headers.foo, 'bar'); }); @@ -257,7 +261,7 @@ test('async handlers', withServer, async (t, server, got) => { return result; } - ] as HandlerFunction[] + ] }); const promise = instance(''); diff --git a/test/error.ts b/test/error.ts index 7653c5d51..b3f47ad3e 100644 --- a/test/error.ts +++ b/test/error.ts @@ -171,7 +171,7 @@ test('catches error in mimicResponse', withServer, async (t, server) => { response.end('ok'); }); - const mimicResponse = () => { + const mimicResponse = (): never => { throw new Error('Error in mimic-response'); }; diff --git a/test/headers.ts b/test/headers.ts index 496cf72e1..bafca4868 100644 --- a/test/headers.ts +++ b/test/headers.ts @@ -63,7 +63,7 @@ test('does not set `accept-encoding` header when `options.decompress` is false', const headers = await got({ decompress: false }).json(); - // @ts-ignore Error test + // @ts-ignore Error tests t.false(Reflect.has(headers, 'accept-encoding')); }); @@ -189,7 +189,10 @@ test('throws on null value headers', async t => { headers: { 'user-agent': null } - }), TypeError, 'Use `undefined` instead of `null` to delete HTTP headers'); + }), { + instanceOf: TypeError, + message: 'Use `undefined` instead of `null` to delete the `user-agent` header' + }); }); test('removes undefined value headers', withServer, async (t, server, got) => { diff --git a/test/helpers/types.ts b/test/helpers/types.ts index d0f37f8dd..7f29b810e 100644 --- a/test/helpers/types.ts +++ b/test/helpers/types.ts @@ -1,3 +1,4 @@ +import {Server} from 'http'; import {TestServer} from 'create-test-server'; import * as lolex from 'lolex'; import {Got} from '../../source'; @@ -6,6 +7,10 @@ export interface ExtendedGot extends Got { secure: Got; } +export interface ExtendedHttpServer extends Server { + socketPath: string; +} + export interface ExtendedTestServer extends TestServer { hostname: string; sslHostname: string; diff --git a/test/helpers/with-server.ts b/test/helpers/with-server.ts index bcd12652c..3c732d86e 100644 --- a/test/helpers/with-server.ts +++ b/test/helpers/with-server.ts @@ -4,11 +4,11 @@ import http = require('http'); import tempy = require('tempy'); import createTestServer = require('create-test-server'); import lolex = require('lolex'); -import got, {HandlerFunction} from '../../source'; -import {ExtendedGot, ExtendedTestServer, GlobalClock, InstalledClock} from './types'; +import got, {Defaults} from '../../source'; +import {ExtendedGot, ExtendedHttpServer, ExtendedTestServer, GlobalClock, InstalledClock} from './types'; export type RunTestWithServer = (t: test.ExecutionContext, server: ExtendedTestServer, got: ExtendedGot, clock: GlobalClock) => Promise | void; -export type RunTestWithSocket = (t: test.ExecutionContext, server: any) => Promise | void; +export type RunTestWithSocket = (t: test.ExecutionContext, server: ExtendedHttpServer) => Promise | void; const generateHook = ({install}: {install?: boolean}): test.Macro<[RunTestWithServer]> => async (t, run) => { const clock: GlobalClock = install ? lolex.install() : lolex.createClock(); @@ -19,7 +19,8 @@ const generateHook = ({install}: {install?: boolean}): test.Macro<[RunTestWithSe } }) as ExtendedTestServer; - const options = { + const options: Defaults = { + // @ts-ignore Augmenting for test detection avaTest: t.title, handlers: [ (options, next) => { @@ -34,7 +35,7 @@ const generateHook = ({install}: {install?: boolean}): test.Macro<[RunTestWithSe return result; } - ] as HandlerFunction[] + ] }; const preparedGot = got.extend({prefixUrl: server.url, ...options}) as ExtendedGot; @@ -63,8 +64,8 @@ export const withSocketServer: test.Macro<[RunTestWithSocket]> = async (t, run) const socketPath = tempy.file({extension: 'socket'}); const server = http.createServer((request, response) => { - server.emit(request.url, request, response); - }) as any; + server.emit(request.url!, request, response); + }) as ExtendedHttpServer; server.socketPath = socketPath; diff --git a/test/hooks.ts b/test/hooks.ts index 18b27295e..d1ef53b28 100644 --- a/test/hooks.ts +++ b/test/hooks.ts @@ -386,6 +386,39 @@ test('afterResponse allows to retry', withServer, async (t, server, got) => { t.is(statusCode, 200); }); +test('cancelling the request after retrying in a afterResponse hook', withServer, async (t, server, got) => { + let requests = 0; + server.get('/', (_request, response) => { + requests++; + response.end(); + }); + + const gotPromise = got({ + hooks: { + afterResponse: [ + (_response, retryWithMergedOptions) => { + const promise = retryWithMergedOptions({ + headers: { + token: 'unicorn' + } + }); + + gotPromise.cancel(); + + return promise; + } + ] + }, + retry: { + calculateDelay: () => 1 + } + }); + + await t.throwsAsync(gotPromise); + await delay(100); + t.is(requests, 1); +}); + test('afterResponse allows to retry - `beforeRetry` hook', withServer, async (t, server, got) => { server.get('/', (request, response) => { if (request.headers.token !== 'unicorn') { diff --git a/test/merge-instances.ts b/test/merge-instances.ts index ea6ea2df0..a2b36a186 100644 --- a/test/merge-instances.ts +++ b/test/merge-instances.ts @@ -1,6 +1,6 @@ import test from 'ava'; import {Handler} from 'express'; -import got, {Got, Headers} from '../source'; +import got, {BeforeRequestHook, Got, Headers} from '../source'; import withServer from './helpers/with-server'; const echoHeaders: Handler = (request, response) => { @@ -77,7 +77,7 @@ test('merging two groups of merged instances', withServer, async (t, server) => }); test('hooks are merged', t => { - const getBeforeRequestHooks = (instance: Got) => instance.defaults.options.hooks.beforeRequest; + const getBeforeRequestHooks = (instance: Got): BeforeRequestHook[] => instance.defaults.options.hooks.beforeRequest; const instanceA = got.extend({hooks: { beforeRequest: [ diff --git a/test/options-to-url.ts b/test/options-to-url.ts index 4219fb5b6..bbd5d1fe6 100644 --- a/test/options-to-url.ts +++ b/test/options-to-url.ts @@ -4,21 +4,21 @@ import optionsToUrl from '../source/utils/options-to-url'; test('`path` is deprecated', t => { t.throws(() => { - // @ts-ignore + // @ts-ignore Error tests optionsToUrl({path: ''}); }, 'Parameter `path` is deprecated. Use `pathname` instead.'); }); test('`auth` is deprecated', t => { t.throws(() => { - // @ts-ignore + // @ts-ignore Error tests optionsToUrl({auth: ''}); }, 'Parameter `auth` is deprecated. Use `username`/`password` instead.'); }); test('`search` and `searchParams` are mutually exclusive', t => { t.throws(() => { - // @ts-ignore + // @ts-ignore Error tests optionsToUrl({search: 'a', searchParams: {}}); }, 'Parameters `search` and `searchParams` are mutually exclusive.'); }); diff --git a/test/post.ts b/test/post.ts index 24895ae51..635a9c5b7 100644 --- a/test/post.ts +++ b/test/post.ts @@ -26,9 +26,11 @@ test('GET cannot have body', withServer, async (t, server, got) => { test('invalid body', async t => { await t.throwsAsync( // @ts-ignore Error tests - got('https://example.com', {body: {}}), - TypeError, - 'The `body` option must be a stream.Readable, string or Buffer' + got.post('https://example.com', {body: {}}), + { + instanceOf: TypeError, + message: 'The `body` option must be a stream.Readable, string or Buffer' + } ); }); diff --git a/test/promise.ts b/test/promise.ts index 3c224fb5a..2e4054ce6 100644 --- a/test/promise.ts +++ b/test/promise.ts @@ -1,6 +1,7 @@ import {ClientRequest} from 'http'; import {Transform as TransformStream} from 'stream'; import test from 'ava'; +import {Response} from '../source'; import withServer from './helpers/with-server'; test('emits request event as promise', withServer, async (t, server, got) => { @@ -9,7 +10,7 @@ test('emits request event as promise', withServer, async (t, server, got) => { response.end('null'); }); - await got('').json().on('request', (request: any) => { + await got('').json().on('request', (request: ClientRequest) => { t.true(request instanceof ClientRequest); }); }); @@ -20,7 +21,7 @@ test('emits response event as promise', withServer, async (t, server, got) => { response.end('null'); }); - await got('').json().on('response', (response: any) => { + await got('').json().on('response', (response: Response) => { t.true(response instanceof TransformStream); t.true(response.readable); t.is(response.statusCode, 200); diff --git a/test/retry.ts b/test/retry.ts index d85f5ae07..1986cf315 100644 --- a/test/retry.ts +++ b/test/retry.ts @@ -5,8 +5,7 @@ import test from 'ava'; import is from '@sindresorhus/is'; import {Handler} from 'express'; import pEvent = require('p-event'); -import got, {HTTPError, RequestFunction, RetryOptions} from '../source'; -import {OptionsOfDefaultResponseBody} from '../source/create'; +import got, {HTTPError} from '../source'; import withServer from './helpers/with-server'; const retryAfterOn413 = 2; @@ -19,14 +18,14 @@ const handler413: Handler = (_request, response) => { response.end(); }; -const createSocketTimeoutStream = () => { +const createSocketTimeoutStream = (): http.ClientRequest => { const stream = new PassThroughStream(); - // @ts-ignore + // @ts-ignore Mocking the behaviour of a ClientRequest stream.setTimeout = (ms, callback) => { callback(); }; - // @ts-ignore + // @ts-ignore Mocking the behaviour of a ClientRequest stream.abort = () => {}; stream.resume(); @@ -43,15 +42,20 @@ test('works on timeout', withServer, async (t, server, got) => { timeout: { socket: socketTimeout }, - request: (...[url, options, callback]: Parameters) => { + request: (...args: [ + string | URL | http.RequestOptions, + (http.RequestOptions | ((res: http.IncomingMessage) => void))?, + ((res: http.IncomingMessage) => void)? + ]) => { if (knocks === 1) { - return http.request(url, options, callback); + // @ts-ignore Overload error + return http.request(...args); } knocks++; return createSocketTimeoutStream(); } - } as OptionsOfDefaultResponseBody)).body, 'who`s there?'); + })).body, 'who`s there?'); }); test('retry function gets iteration count', withServer, async (t, server, got) => { @@ -70,7 +74,7 @@ test('retry function gets iteration count', withServer, async (t, server, got) = retry: { calculateDelay: ({attemptCount}) => { t.true(is.number(attemptCount)); - return Number(attemptCount < 2); + return attemptCount < 2 ? 1 : 0; } } }); @@ -127,7 +131,7 @@ test('custom error codes', async t => { const error = await t.throwsAsync(got('https://example.com', { request: () => { - const emitter = new EventEmitter() as any; + const emitter = new EventEmitter() as http.ClientRequest; emitter.end = () => {}; const error = new Error('Snap!'); @@ -140,7 +144,7 @@ test('custom error codes', async t => { }, retry: { calculateDelay: ({error}) => { - t.is(error.code as unknown as typeof errorCode, errorCode); + t.is(error.code as string as typeof errorCode, errorCode); return 0; }, methods: [ @@ -252,13 +256,14 @@ test('retries on 503 without Retry-After header', withServer, async (t, server, test('doesn\'t retry on streams', withServer, async (t, server, got) => { server.get('/', () => {}); + // @ts-ignore Error tests const stream = got.stream({ timeout: 1, retry: { retries: () => { t.fail('Retries on streams'); } - } as unknown as RetryOptions + } }); await t.throwsAsync(pEvent(stream, 'response')); }); diff --git a/test/socket-destroyed.ts b/test/socket-destroyed.ts index 57f5f8a20..63e476e81 100644 --- a/test/socket-destroyed.ts +++ b/test/socket-destroyed.ts @@ -5,14 +5,16 @@ import got from '../source'; // https://github.com/nodejs/node/pull/21453 // eslint-disable-next-line ava/no-skip-test test.skip('clear the progressInterval if the socket has been destroyed', async t => { - // @ts-ignore + // @ts-ignore process.binding is an internal API, + // and no consensus have been made to add it to the types + // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/31118 const {Timer} = process.binding('timer_wrap'); // eslint-disable-line node/no-deprecated-api await t.throwsAsync(got('http://127.0.0.1:55555', {retry: 0}), { code: 'ECONNREFUSED' }); - // @ts-ignore + // @ts-ignore process._getActiveHandles is an internal API const progressIntervalTimer = process._getActiveHandles().filter(handle => { // Check if the handle is a Timer that matches the `uploadEventFrequency` interval return handle instanceof Timer && handle._list.msecs === 150; diff --git a/test/timeout.ts b/test/timeout.ts index b6fdbd033..0f1580a83 100644 --- a/test/timeout.ts +++ b/test/timeout.ts @@ -10,7 +10,6 @@ import CacheableLookup from 'cacheable-lookup'; import {Handler} from 'express'; import pEvent = require('p-event'); import got from '../source'; -import {OptionsOfDefaultResponseBody} from '../source/create'; import timedOut from '../source/utils/timed-out'; import slowDataStream from './helpers/slow-data-stream'; import {GlobalClock} from './helpers/types'; @@ -85,12 +84,12 @@ test.serial('socket timeout', async t => { retry: 0, request: () => { const stream = new PassThroughStream(); - // @ts-ignore + // @ts-ignore Mocking the behaviour of a ClientRequest stream.setTimeout = (ms, callback) => { callback(); }; - // @ts-ignore + // @ts-ignore Mocking the behaviour of a ClientRequest stream.abort = () => {}; stream.resume(); @@ -223,8 +222,8 @@ test.serial('connect timeout', withServerAndLolex, async (t, _server, got, clock await t.throwsAsync( got({ createConnection: options => { - const socket = new net.Socket(options as unknown as net.SocketConstructorOpts); - // @ts-ignore + const socket = new net.Socket(options as object as net.SocketConstructorOpts); + // @ts-ignore We know that it is readonly, but we have to test it socket.connecting = true; setImmediate(() => { socket.emit('lookup', null, '127.0.0.1', 4, 'localhost'); @@ -250,8 +249,8 @@ test.serial('connect timeout (ip address)', withServerAndLolex, async (t, _serve got({ hostname: '127.0.0.1', createConnection: options => { - const socket = new net.Socket(options as unknown as net.SocketConstructorOpts); - // @ts-ignore + const socket = new net.Socket(options as object as net.SocketConstructorOpts); + // @ts-ignore We know that it is readonly, but we have to test it socket.connecting = true; return socket; }, @@ -273,9 +272,8 @@ test.serial('secureConnect timeout', withServerAndLolex, async (t, _server, got, await t.throwsAsync( got.secure({ createConnection: options => { - // @ts-ignore - const socket = new net.Socket(options); - // @ts-ignore + const socket = new net.Socket(options as object as net.SocketConstructorOpts); + // @ts-ignore We know that it is readonly, but we have to test it socket.connecting = true; setImmediate(() => { socket.emit('lookup', null, '127.0.0.1', 4, 'localhost'); @@ -317,6 +315,7 @@ test.serial('lookup timeout', withServerAndLolex, async (t, server, got, clock) await t.throwsAsync( got({ + // @ts-ignore Manual tests lookup: () => {}, timeout: {lookup: 1}, retry: 0 @@ -441,7 +440,7 @@ test.serial('no more timeouts after an error', withServerAndLolex, async (t, _se } }).on('request', () => { const {setTimeout} = global; - // @ts-ignore + // @ts-ignore Augmenting global for testing purposes global.setTimeout = (callback, _ms, ...args) => { callback(...args); @@ -511,7 +510,7 @@ test('ensure there are no new timeouts after cancelation', t => { test('double calling timedOut has no effect', t => { const emitter = new EventEmitter(); - const attach = () => timedOut(emitter as http.ClientRequest, { + const attach = (): () => void => timedOut(emitter as http.ClientRequest, { connect: 1 }, { hostname: '127.0.0.1' @@ -528,6 +527,7 @@ test.serial('doesn\'t throw on early lookup', withServerAndLolex, async (t, serv response.end('ok'); }); + // @ts-ignore await t.notThrowsAsync(got('', { timeout: { lookup: 1 @@ -541,5 +541,5 @@ test.serial('doesn\'t throw on early lookup', withServerAndLolex, async (t, serv // @ts-ignore This should be fixed in upstream callback(null, '127.0.0.1', 4); } - } as OptionsOfDefaultResponseBody)); + })); }); diff --git a/test/types/create-test-server/index.d.ts b/test/types/create-test-server/index.d.ts index c85b3f4ee..970cb5684 100644 --- a/test/types/create-test-server/index.d.ts +++ b/test/types/create-test-server/index.d.ts @@ -1,13 +1,13 @@ declare module 'create-test-server' { import {Express} from 'express'; - function createTestServer(options: any): Promise; + function createTestServer(options: unknown): Promise; export = createTestServer; namespace createTestServer { export interface TestServer extends Express { - caCert: any; + caCert: string | Buffer | Array; port: number; url: string; sslPort: number;