diff --git a/packages/core/src/RequestUtils.js b/packages/core/src/RequestUtils.js index fd3ec4c8..5026df40 100644 --- a/packages/core/src/RequestUtils.js +++ b/packages/core/src/RequestUtils.js @@ -127,14 +127,16 @@ export function getQuery(url) { /** * - * @param {Headers | [string, string][] | Record < string, string > | Object. | HeadersInit} headers + * @param {HeadersInit | Object.} headers * @returns {Object.} */ export const normalizeHeaders = (headers) => { - let entries = headers; + let entries; if (headers instanceof Headers) { entries = [...headers.entries()]; - } else if (!Array.isArray(headers)) { + } else if (Array.isArray(headers)) { + entries = headers; + } else { entries = Object.entries(headers); } return Object.fromEntries( diff --git a/packages/core/src/Router.js b/packages/core/src/Router.js index 469e54bc..3fb01ca1 100644 --- a/packages/core/src/Router.js +++ b/packages/core/src/Router.js @@ -93,33 +93,27 @@ function shouldSendAsObject(responseInput) { return true; } -function throwSpecExceptions(callLog) { - const headers = callLog.options.headers; +/** + * + * @param {CallLog} callLog + */ +function throwSpecExceptions({ url, options: { headers, method, body } }) { if (headers) { Object.entries(headers).forEach(([key]) => { if (/\s/.test(key)) { - throw new TypeError('no way - space in header'); + throw new TypeError('Invalid name'); } - - // if (/\s/.test(value)) { - // throw new TypeError('no way - space in header'); - // } }); } - if (/^[a-z]+\:\/\/[^:]+:[^@]+@/.test(callLog.url)) { - throw new TypeError('no way - contains credentials'); - } - - if (['navigate', 'websocket'].includes(callLog.options.mode)) { - throw new TypeError('no way - wrong mode'); + const urlObject = new URL(url); + if (urlObject.username || urlObject.password) { + throw new TypeError( + `Request cannot be constructed from a URL that includes credentials: ${url}`, + ); } - console.log(callLog.options); - if ( - ['get', 'head'].includes(callLog.options.method) && - callLog.options.body - ) { - throw new TypeError('no way - wrong method for body'); + if (['get', 'head'].includes(method) && body) { + throw new TypeError('Request with GET/HEAD method cannot have body.'); } } diff --git a/packages/core/src/__tests__/CallHistory.test.js b/packages/core/src/__tests__/CallHistory.test.js index d3ad9b1d..adacddf2 100644 --- a/packages/core/src/__tests__/CallHistory.test.js +++ b/packages/core/src/__tests__/CallHistory.test.js @@ -9,7 +9,7 @@ describe('CallHistory', () => { }); const fetchTheseUrls = (...urls) => - Promise.all(urls.map(fm.fetchHandler.bind(fm))); + Promise.all(urls.map((url) => fm.fetchHandler(url))); describe('helper methods', () => { describe('called()', () => { @@ -208,8 +208,8 @@ describe('CallHistory', () => { describe('boolean and named route filters', () => { it('can retrieve calls matched by non-fallback routes', async () => { fm.route('http://a.com/', 200).catch(); - await fetchTheseUrls('http://a.com/', 'http://b.com/'); + expectSingleUrl(true)('http://a.com/'); expectSingleUrl('matched')('http://a.com/'); }); @@ -298,12 +298,13 @@ describe('CallHistory', () => { headers: { a: 'z' }, }); expect(filteredCalls.length).toEqual(1); + expect(filteredCalls[0]).toMatchObject( expect.objectContaining({ url: 'http://a.com/', - options: { + options: expect.objectContaining({ headers: { a: 'z' }, - }, + }), }), ); }); @@ -326,9 +327,9 @@ describe('CallHistory', () => { expect(filteredCalls[0]).toMatchObject( expect.objectContaining({ url: 'http://b.com/', - options: { + options: expect.objectContaining({ headers: { a: 'z' }, - }, + }), }), ); }); @@ -347,9 +348,9 @@ describe('CallHistory', () => { expect(filteredCalls[0]).toMatchObject( expect.objectContaining({ url: 'http://a.com/', - options: { + options: expect.objectContaining({ headers: { a: 'z' }, - }, + }), }), ); }); diff --git a/packages/core/src/__tests__/spec-compliance.test.js b/packages/core/src/__tests__/spec-compliance.test.js index c29f0232..994b82a6 100644 --- a/packages/core/src/__tests__/spec-compliance.test.js +++ b/packages/core/src/__tests__/spec-compliance.test.js @@ -12,38 +12,33 @@ describe('Spec compliance', () => { 'has space': 'ok', }, }), - ).rejects.toThrow(new TypeError('asdasdsa')); - }); - it('reject on invalid header value', async () => { - await expect( - fetchMock.fetchHandler('http://a.com', { - headers: [['a', 'b', 'c']], - }), - ).rejects.toThrow(new TypeError('asdasdsa')); + ).rejects.toThrow(new TypeError('Invalid name')); }); it('reject on url containing credentials', async () => { await expect( fetchMock.fetchHandler('http://user:password@a.com'), - ).rejects.toThrow(new TypeError('asdasdsa')); - }); - it('reject on invalid modes', async () => { - await expect( - fetchMock.fetchHandler('http://a.com', { mode: 'websocket' }), - ).rejects.toThrow(new TypeError('asdasdsa')); - await expect( - fetchMock.fetchHandler('http://a.com', { mode: 'navigate' }), - ).rejects.toThrow(new TypeError('asdasdsa')); + ).rejects.toThrow( + new TypeError( + 'Request cannot be constructed from a URL that includes credentials: http://user:password@a.com/', + ), + ); }); it('reject if the request method is GET or HEAD and the body is non-null.', async () => { await expect( fetchMock.fetchHandler('http://a.com', { body: 'a' }), - ).rejects.toThrow(new TypeError('asdasdsa')); + ).rejects.toThrow( + new TypeError('Request with GET/HEAD method cannot have body.'), + ); await expect( fetchMock.fetchHandler('http://a.com', { body: 'a', method: 'GET' }), - ).rejects.toThrow(new TypeError('asdasdsa')); + ).rejects.toThrow( + new TypeError('Request with GET/HEAD method cannot have body.'), + ); await expect( fetchMock.fetchHandler('http://a.com', { body: 'a', method: 'HEAD' }), - ).rejects.toThrow(new TypeError('asdasdsa')); + ).rejects.toThrow( + new TypeError('Request with GET/HEAD method cannot have body.'), + ); }); }); }); diff --git a/packages/core/types/RequestUtils.d.ts b/packages/core/types/RequestUtils.d.ts index 12db62ed..20560f1f 100644 --- a/packages/core/types/RequestUtils.d.ts +++ b/packages/core/types/RequestUtils.d.ts @@ -3,9 +3,9 @@ export function createCallLogFromUrlAndOptions(url: string | object, options: Re export function createCallLogFromRequest(request: Request, options: RequestInit): Promise; export function getPath(url: string): string; export function getQuery(url: string): string; -export function normalizeHeaders(headers: Headers | [string, string][] | Record | { +export function normalizeHeaders(headers: HeadersInit | { [x: string]: string | number; -} | HeadersInit): { +}): { [x: string]: string; }; export type DerivedRequestOptions = {