From 6e8c20f74cb41c4ea368cabf87cf4a00489c19f2 Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sat, 27 Jul 2019 22:43:37 +0100 Subject: [PATCH 01/12] begin long job of adding debug logs --- package.json | 1 + src/lib/compile-route.js | 11 +++++++++++ src/lib/inspecting.js | 3 +++ 3 files changed, 15 insertions(+) diff --git a/package.json b/package.json index d790d89d..04642d07 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "dependencies": { "babel-runtime": "^6.26.0", "core-js": "^3.0.0", + "debug": "^4.1.1", "glob-to-regexp": "^0.4.0", "lodash.isequal": "^4.5.0", "path-to-regexp": "^2.2.1", diff --git a/src/lib/compile-route.js b/src/lib/compile-route.js index 12d4e7d0..26a0d320 100644 --- a/src/lib/compile-route.js +++ b/src/lib/compile-route.js @@ -1,3 +1,4 @@ +const debug = require('debug')('fetch-mock') const generateMatcher = require('./generate-matcher'); const matcherProperties = [ @@ -36,9 +37,11 @@ const argsToRoute = args => { }; const sanitizeRoute = route => { + debug('Sanitizing route properties'); route = Object.assign({}, route); if (route.method) { + debug(`Converting method ${route.method} to lower case`); route.method = route.method.toLowerCase(); } if (isUrlMatcher(route.matcher)) { @@ -48,7 +51,11 @@ const sanitizeRoute = route => { route.functionMatcher = route.matcher || route.functionMatcher; + debug('Setting route.identifier...') + debug(`- route.name is ${route.name}`) + debug(`- route.matcher is ${route.matcher}`) route.identifier = route.name || route.url || route.functionMatcher; + debug(`=> route.identifier set to ${route.identifier}`); return route; }; @@ -65,10 +72,13 @@ const validateRoute = route => { }; const limitMatcher = route => { + debug('Limiting number of requests to handle by route'); if (!route.repeat) { + debug('No `repeat` value set on route. Will match any number of requests') return; } + debug(`Route set to repeat ${route.repeat} times`) const matcher = route.matcher; let timesLeft = route.repeat; route.matcher = (url, options) => { @@ -91,6 +101,7 @@ const delayResponse = route => { }; const compileRoute = function(args) { + debug('Compiling route'); const route = sanitizeRoute(argsToRoute(args)); validateRoute(route); route.matcher = generateMatcher(route); diff --git a/src/lib/inspecting.js b/src/lib/inspecting.js index 65baed9c..45cddbc3 100644 --- a/src/lib/inspecting.js +++ b/src/lib/inspecting.js @@ -1,3 +1,4 @@ +const debug = require('debug')('fetch-mock') const { normalizeUrl } = require('./request-utils'); const FetchMock = {}; const { sanitizeRoute } = require('./compile-route'); @@ -6,7 +7,9 @@ const isName = nameOrMatcher => typeof nameOrMatcher === 'string' && /^[\da-zA-Z\-]+$/.test(nameOrMatcher); const filterCallsWithMatcher = (matcher, options = {}, calls) => { + debug('/** Begin generating synthetic matcher for inspecting **/') matcher = generateMatcher(sanitizeRoute(Object.assign({ matcher }, options))); + debug('/** End generating synthetic matcher for inspecting **/') return calls.filter(([url, options]) => matcher(normalizeUrl(url), options)); }; From 03cd63d96d9bce9b99825a8c7ad853b954cce025 Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sat, 27 Jul 2019 23:01:12 +0100 Subject: [PATCH 02/12] avod compile debug logs when inspecting --- src/lib/compile-route.js | 14 +++++++------- src/lib/inspecting.js | 5 +---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/lib/compile-route.js b/src/lib/compile-route.js index 26a0d320..49702aae 100644 --- a/src/lib/compile-route.js +++ b/src/lib/compile-route.js @@ -36,12 +36,12 @@ const argsToRoute = args => { return routeConfig; }; -const sanitizeRoute = route => { - debug('Sanitizing route properties'); +const sanitizeRoute = (route, useDebugger = true) => { + useDebugger && debug('Sanitizing route properties'); route = Object.assign({}, route); if (route.method) { - debug(`Converting method ${route.method} to lower case`); + useDebugger && debug(`Converting method ${route.method} to lower case`); route.method = route.method.toLowerCase(); } if (isUrlMatcher(route.matcher)) { @@ -51,11 +51,11 @@ const sanitizeRoute = route => { route.functionMatcher = route.matcher || route.functionMatcher; - debug('Setting route.identifier...') - debug(`- route.name is ${route.name}`) - debug(`- route.matcher is ${route.matcher}`) + useDebugger && debug('Setting route.identifier...') + useDebugger && debug(`- route.name is ${route.name}`) + useDebugger && debug(`- route.matcher is ${route.matcher}`) route.identifier = route.name || route.url || route.functionMatcher; - debug(`=> route.identifier set to ${route.identifier}`); + useDebugger && debug(`=> route.identifier set to ${route.identifier}`); return route; }; diff --git a/src/lib/inspecting.js b/src/lib/inspecting.js index 45cddbc3..3e763829 100644 --- a/src/lib/inspecting.js +++ b/src/lib/inspecting.js @@ -1,4 +1,3 @@ -const debug = require('debug')('fetch-mock') const { normalizeUrl } = require('./request-utils'); const FetchMock = {}; const { sanitizeRoute } = require('./compile-route'); @@ -7,9 +6,7 @@ const isName = nameOrMatcher => typeof nameOrMatcher === 'string' && /^[\da-zA-Z\-]+$/.test(nameOrMatcher); const filterCallsWithMatcher = (matcher, options = {}, calls) => { - debug('/** Begin generating synthetic matcher for inspecting **/') - matcher = generateMatcher(sanitizeRoute(Object.assign({ matcher }, options))); - debug('/** End generating synthetic matcher for inspecting **/') + matcher = generateMatcher(sanitizeRoute(Object.assign({ matcher }, options), false)); return calls.filter(([url, options]) => matcher(normalizeUrl(url), options)); }; From e18b7ff2490bd5058818cdc7edf4c453d191f7d3 Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sat, 27 Jul 2019 23:19:51 +0100 Subject: [PATCH 03/12] debug logs for fetch handler function --- src/lib/compile-route.js | 6 +++--- src/lib/fetch-handler.js | 33 +++++++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/lib/compile-route.js b/src/lib/compile-route.js index 49702aae..caa49661 100644 --- a/src/lib/compile-route.js +++ b/src/lib/compile-route.js @@ -52,10 +52,10 @@ const sanitizeRoute = (route, useDebugger = true) => { route.functionMatcher = route.matcher || route.functionMatcher; useDebugger && debug('Setting route.identifier...') - useDebugger && debug(`- route.name is ${route.name}`) - useDebugger && debug(`- route.matcher is ${route.matcher}`) + useDebugger && debug(` route.name is ${route.name}`) + useDebugger && debug(` route.matcher is ${route.matcher}`) route.identifier = route.name || route.url || route.functionMatcher; - useDebugger && debug(`=> route.identifier set to ${route.identifier}`); + useDebugger && debug(` > route.identifier set to ${route.identifier}`); return route; }; diff --git a/src/lib/fetch-handler.js b/src/lib/fetch-handler.js index 971aef71..0ce7f0e0 100755 --- a/src/lib/fetch-handler.js +++ b/src/lib/fetch-handler.js @@ -1,3 +1,4 @@ +const debug = require('debug')('fetch-mock') const responseBuilder = require('./response-builder'); const requestUtils = require('./request-utils'); const FetchMock = {}; @@ -23,6 +24,7 @@ const resolve = async ( options, request ) => { + debug('Recursively resolving function and promise responses') // We want to allow things like // - function returning a Promise for a response // - delaying (using a timeout Promise) a function's execution to generate @@ -32,22 +34,36 @@ const resolve = async ( // have something that looks like neither Promise nor function while (true) { if (typeof response === 'function') { + debug(' Response is a function') // in the case of falling back to the network we need to make sure we're using // the original Request instance, not our normalised url + options - response = - request && responseIsFetch - ? response(request) - : response(url, options, request); + if (responseIsFetch) { + if (request) { + debug(' > Calling fetch with Request instance') + return response(request) + } + debug(' > Calling fetch with url and options') + return response(url, options); + } else { + debug(' > Calling custom matcher function') + return response(url, options, request); + } } else if (typeof response.then === 'function') { + debug(' Response is a promise') + debug(' > Resolving promise') response = await response; } else { + debug(' Response is not a function or a promise') + debug(' > Returning response for conversion into Response instance') return response; } } }; FetchMock.fetchHandler = function(url, options, request) { + debug('**HANDLING NEW FETCH**'); const normalizedRequest = requestUtils.normalizeRequest( + ({ url, options, request } = requestUtils.normalizeRequest( url, options, this.config.Request @@ -67,6 +83,7 @@ FetchMock.fetchHandler = function(url, options, request) { // constructors defined by the user return new this.config.Promise((res, rej) => { if (signal) { + debug('options.signal exists - setting up fetch aborting') const abort = () => { // note that DOMException is not available in node.js; even node-fetch uses a custom error class: https://github.com/bitinn/node-fetch/blob/master/src/abort-error.js rej( @@ -77,6 +94,7 @@ FetchMock.fetchHandler = function(url, options, request) { done(); }; if (signal.aborted) { + debug('options.signal is already aborted- abort the fetch') abort(); } signal.addEventListener('abort', abort); @@ -91,13 +109,16 @@ FetchMock.fetchHandler = function(url, options, request) { FetchMock.fetchHandler.isMock = true; FetchMock.executeRouter = function(url, options, request) { + debug('Attempting to match request to defined routes') if (this.config.fallbackToNetwork === 'always') { + debug(' Configured with fallbackToNetwork=always - passing through to fetch') return { response: this.getNativeFetch(), responseIsFetch: true }; } const match = this.router(url, options, request); if (match) { + debug(' Matching route found') return match; } @@ -108,6 +129,7 @@ FetchMock.executeRouter = function(url, options, request) { this.push({ url, options, request, isUnmatched: true }); if (this.fallbackResponse) { + debug(' No matching route found - using fallbackResponse') return { response: this.fallbackResponse }; } @@ -119,6 +141,7 @@ FetchMock.executeRouter = function(url, options, request) { ); } + debug(' Configured to fallbackToNetwork - passing through to fetch') return { response: this.getNativeFetch(), responseIsFetch: true }; }; @@ -128,11 +151,13 @@ FetchMock.generateResponse = async function(route, url, options, request) { // If the response says to throw an error, throw it // Type checking is to deal with sinon spies having a throws property :-0 if (response.throws && typeof response !== 'function') { + debug('response.throws is defined - throwing an error') throw response.throws; } // If the response is a pre-made Response, respond with it if (this.config.Response.prototype.isPrototypeOf(response)) { + debug('response is already a Response instance - returning it') return response; } From 4fa4c69229f350fa474e6a98260b46c0816cb47d Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sat, 27 Jul 2019 23:20:44 +0100 Subject: [PATCH 04/12] debug logs for fetch handler function --- src/lib/generate-matcher.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/generate-matcher.js b/src/lib/generate-matcher.js index baae61be..8e33ac42 100644 --- a/src/lib/generate-matcher.js +++ b/src/lib/generate-matcher.js @@ -1,3 +1,4 @@ +const debug = require('debug')('fetch-mock'); const glob = require('glob-to-regexp'); const pathToRegexp = require('path-to-regexp'); const querystring = require('querystring'); @@ -133,6 +134,7 @@ const getUrlMatcher = route => { }; module.exports = route => { + debug('Generating matcher for route') const matchers = [ route.query && getQueryStringMatcher(route), route.method && getMethodMatcher(route), From c5f6c20c6d532068c2a543aec87fa3fbdc7a40f4 Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sat, 27 Jul 2019 23:23:38 +0100 Subject: [PATCH 05/12] minor refactor to make debug logging easier in matcher building --- src/lib/generate-matcher.js | 30 +++++++++++++++++++++++++++--- src/lib/inspecting.js | 2 +- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/lib/generate-matcher.js b/src/lib/generate-matcher.js index 8e33ac42..2de2543a 100644 --- a/src/lib/generate-matcher.js +++ b/src/lib/generate-matcher.js @@ -25,6 +25,9 @@ const stringMatchers = { }; const getHeaderMatcher = ({ headers: expectedHeaders }) => { + if (!expectedHeaders) { + return + } const expectation = headerUtils.toLowerCase(expectedHeaders); return (url, { headers = {} }) => { const lowerCaseHeaders = headerUtils.toLowerCase( @@ -38,11 +41,17 @@ const getHeaderMatcher = ({ headers: expectedHeaders }) => { }; const getMethodMatcher = ({ method: expectedMethod }) => { + if (!expectedMethod) { + return + } return (url, { method }) => expectedMethod === (method ? method.toLowerCase() : 'get'); }; const getQueryStringMatcher = ({ query: expectedQuery }) => { + if (!expectedQuery) { + return + } const keys = Object.keys(expectedQuery); return url => { const query = querystring.parse(getQuery(url)); @@ -50,15 +59,18 @@ const getQueryStringMatcher = ({ query: expectedQuery }) => { }; }; -const getParamsMatcher = ({ params: expectedParams, url: matcheUrl }) => { - if (!/express:/.test(matcheUrl)) { +const getParamsMatcher = ({ params: expectedParams, url: matcherUrl }) => { + if (!expectedParams) { + return + } + if (!/express:/.test(matcherUrl)) { throw new Error( 'fetch-mock: matching on params is only possible when using an express: matcher' ); } const expectedKeys = Object.keys(expectedParams); const keys = []; - const re = pathToRegexp(matcheUrl.replace(/^express:/, ''), keys); + const re = pathToRegexp(matcherUrl.replace(/^express:/, ''), keys); return url => { const vals = re.exec(getPath(url)) || []; vals.shift(); @@ -106,6 +118,7 @@ const getFullUrlMatcher = (route, matcherUrl, query) => { }; }; +<<<<<<< HEAD const getFunctionMatcher = ({ functionMatcher }) => functionMatcher; const getUrlMatcher = route => { @@ -143,6 +156,17 @@ module.exports = route => { route.body && getBodyMatcher(route), route.functionMatcher && getFunctionMatcher(route), route.url && getUrlMatcher(route) +======= +module.exports = (route, useDebugger = true) => { + useDebugger && debug('Compiling matcher for route') + const matchers = [ + getQueryStringMatcher(route), + getMethodMatcher(route), + getHeaderMatcher(route), + getParamsMatcher(route), + getFunctionMatcher(route), + getUrlMatcher(route) +>>>>>>> minor refactor to make debug logging easier in matcher building ].filter(matcher => !!matcher); return (url, options = {}, request) => diff --git a/src/lib/inspecting.js b/src/lib/inspecting.js index 3e763829..e5d7dd59 100644 --- a/src/lib/inspecting.js +++ b/src/lib/inspecting.js @@ -6,7 +6,7 @@ const isName = nameOrMatcher => typeof nameOrMatcher === 'string' && /^[\da-zA-Z\-]+$/.test(nameOrMatcher); const filterCallsWithMatcher = (matcher, options = {}, calls) => { - matcher = generateMatcher(sanitizeRoute(Object.assign({ matcher }, options), false)); + matcher = generateMatcher(sanitizeRoute(Object.assign({ matcher }, options), false), false); return calls.filter(([url, options]) => matcher(normalizeUrl(url), options)); }; From 959288f475eb7a64fc681e7bb58739a75acd0031 Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sun, 28 Jul 2019 00:17:52 +0100 Subject: [PATCH 06/12] most matcher debug logs written --- src/lib/compile-route.js | 6 +- src/lib/fetch-handler.js | 38 ++++++------ src/lib/generate-matcher.js | 112 +++++++++++++++++++++++++++--------- src/lib/inspecting.js | 5 +- 4 files changed, 111 insertions(+), 50 deletions(-) diff --git a/src/lib/compile-route.js b/src/lib/compile-route.js index caa49661..50977b88 100644 --- a/src/lib/compile-route.js +++ b/src/lib/compile-route.js @@ -1,4 +1,4 @@ -const debug = require('debug')('fetch-mock') +const debug = require('debug')('fetch-mock'); const generateMatcher = require('./generate-matcher'); const matcherProperties = [ @@ -74,11 +74,11 @@ const validateRoute = route => { const limitMatcher = route => { debug('Limiting number of requests to handle by route'); if (!route.repeat) { - debug('No `repeat` value set on route. Will match any number of requests') + debug('No `repeat` value set on route. Will match any number of requests'); return; } - debug(`Route set to repeat ${route.repeat} times`) + debug(`Route set to repeat ${route.repeat} times`); const matcher = route.matcher; let timesLeft = route.repeat; route.matcher = (url, options) => { diff --git a/src/lib/fetch-handler.js b/src/lib/fetch-handler.js index 0ce7f0e0..d55c2315 100755 --- a/src/lib/fetch-handler.js +++ b/src/lib/fetch-handler.js @@ -1,4 +1,4 @@ -const debug = require('debug')('fetch-mock') +const debug = require('debug')('fetch-mock'); const responseBuilder = require('./response-builder'); const requestUtils = require('./request-utils'); const FetchMock = {}; @@ -24,7 +24,7 @@ const resolve = async ( options, request ) => { - debug('Recursively resolving function and promise responses') + debug('Recursively resolving function and promise responses'); // We want to allow things like // - function returning a Promise for a response // - delaying (using a timeout Promise) a function's execution to generate @@ -34,27 +34,27 @@ const resolve = async ( // have something that looks like neither Promise nor function while (true) { if (typeof response === 'function') { - debug(' Response is a function') + debug(' Response is a function'); // in the case of falling back to the network we need to make sure we're using // the original Request instance, not our normalised url + options if (responseIsFetch) { if (request) { - debug(' > Calling fetch with Request instance') - return response(request) + debug(' > Calling fetch with Request instance'); + return response(request); } - debug(' > Calling fetch with url and options') + debug(' > Calling fetch with url and options'); return response(url, options); } else { - debug(' > Calling custom matcher function') + debug(' > Calling custom matcher function'); return response(url, options, request); } } else if (typeof response.then === 'function') { - debug(' Response is a promise') - debug(' > Resolving promise') + debug(' Response is a promise'); + debug(' > Resolving promise'); response = await response; } else { - debug(' Response is not a function or a promise') - debug(' > Returning response for conversion into Response instance') + debug(' Response is not a function or a promise'); + debug(' > Returning response for conversion into Response instance'); return response; } } @@ -109,16 +109,18 @@ FetchMock.fetchHandler = function(url, options, request) { FetchMock.fetchHandler.isMock = true; FetchMock.executeRouter = function(url, options, request) { - debug('Attempting to match request to defined routes') + debug('Attempting to match request to defined routes'); if (this.config.fallbackToNetwork === 'always') { - debug(' Configured with fallbackToNetwork=always - passing through to fetch') + debug( + ' Configured with fallbackToNetwork=always - passing through to fetch' + ); return { response: this.getNativeFetch(), responseIsFetch: true }; } const match = this.router(url, options, request); if (match) { - debug(' Matching route found') + debug(' Matching route found'); return match; } @@ -129,7 +131,7 @@ FetchMock.executeRouter = function(url, options, request) { this.push({ url, options, request, isUnmatched: true }); if (this.fallbackResponse) { - debug(' No matching route found - using fallbackResponse') + debug(' No matching route found - using fallbackResponse'); return { response: this.fallbackResponse }; } @@ -141,7 +143,7 @@ FetchMock.executeRouter = function(url, options, request) { ); } - debug(' Configured to fallbackToNetwork - passing through to fetch') + debug(' Configured to fallbackToNetwork - passing through to fetch'); return { response: this.getNativeFetch(), responseIsFetch: true }; }; @@ -151,13 +153,13 @@ FetchMock.generateResponse = async function(route, url, options, request) { // If the response says to throw an error, throw it // Type checking is to deal with sinon spies having a throws property :-0 if (response.throws && typeof response !== 'function') { - debug('response.throws is defined - throwing an error') + debug('response.throws is defined - throwing an error'); throw response.throws; } // If the response is a pre-made Response, respond with it if (this.config.Response.prototype.isPrototypeOf(response)) { - debug('response is already a Response instance - returning it') + debug('response is already a Response instance - returning it'); return response; } diff --git a/src/lib/generate-matcher.js b/src/lib/generate-matcher.js index 2de2543a..1e351375 100644 --- a/src/lib/generate-matcher.js +++ b/src/lib/generate-matcher.js @@ -10,30 +10,42 @@ const { } = require('./request-utils'); const isEqual = require('lodash.isequal'); +const debuggableUrlFunc = func => url => { + debug('Actual url:', url); + return func(url); +}; + const stringMatchers = { - begin: targetString => url => url.indexOf(targetString) === 0, - end: targetString => url => url.substr(-targetString.length) === targetString, + begin: targetString => + debuggableUrlFunc(url => url.indexOf(targetString) === 0), + end: targetString => + debuggableUrlFunc(url => url.substr(-targetString.length) === targetString), glob: targetString => { const urlRX = glob(targetString); - return url => urlRX.test(url); + return debuggableUrlFunc(url => urlRX.test(url)); }, express: targetString => { const urlRX = pathToRegexp(targetString); - return url => urlRX.test(getPath(url)); + return debuggableUrlFunc(url => urlRX.test(getPath(url))); }, - path: targetString => url => getPath(url) === targetString + path: targetString => debuggableUrlFunc(url => getPath(url) === targetString) }; const getHeaderMatcher = ({ headers: expectedHeaders }) => { + debug('Generating header matcher'); if (!expectedHeaders) { - return + debug(' No header expectations defined - skipping'); + return; } const expectation = headerUtils.toLowerCase(expectedHeaders); + debug(' Expected headers:', expectation); return (url, { headers = {} }) => { + debug('Attempting to match headers'); const lowerCaseHeaders = headerUtils.toLowerCase( headerUtils.normalize(headers) ); - + debug(' Expected headers:', expectation); + debug(' Actual headers:', lowerCaseHeaders); return Object.keys(expectation).every(headerName => headerUtils.equal(lowerCaseHeaders[headerName], expectation[headerName]) ); @@ -41,37 +53,55 @@ const getHeaderMatcher = ({ headers: expectedHeaders }) => { }; const getMethodMatcher = ({ method: expectedMethod }) => { + debug('Generating method matcher'); if (!expectedMethod) { - return + debug(' No method expectations defined - skipping'); + return; } - return (url, { method }) => - expectedMethod === (method ? method.toLowerCase() : 'get'); + debug(' Expected method:', expectedMethod); + return (url, { method }) => { + debug('Attempting to match method'); + const actualMethod = method ? method.toLowerCase() : 'get'; + debug(' Expected method:', expectedMethod); + debug(' Actual method:', actualMethod); + return expectedMethod === actualMethod; + }; }; const getQueryStringMatcher = ({ query: expectedQuery }) => { + debug('Generating query parameters matcher'); if (!expectedQuery) { - return + debug(' No query parameters expectations defined - skipping'); + return; } + debug(' Expected query parameters:', expectedQuery); const keys = Object.keys(expectedQuery); return url => { + debug('Attempting to match query parameters'); const query = querystring.parse(getQuery(url)); + debug(' Expected query parameters:', expectedQuery); + debug(' Actual query parameters:', query); return keys.every(key => query[key] === expectedQuery[key]); }; }; const getParamsMatcher = ({ params: expectedParams, url: matcherUrl }) => { + debug('Generating path parameters matcher'); if (!expectedParams) { - return + debug(' No path parameters expectations defined - skipping'); + return; } if (!/express:/.test(matcherUrl)) { throw new Error( 'fetch-mock: matching on params is only possible when using an express: matcher' ); } + debug(' Expected path parameters:', expectedParams); const expectedKeys = Object.keys(expectedParams); const keys = []; const re = pathToRegexp(matcherUrl.replace(/^express:/, ''), keys); return url => { + debug('Attempting to match path parameters'); const vals = re.exec(getPath(url)) || []; vals.shift(); const params = keys.reduce( @@ -79,13 +109,18 @@ const getParamsMatcher = ({ params: expectedParams, url: matcherUrl }) => { vals[i] ? Object.assign(map, { [name]: vals[i] }) : map, {} ); + debug(' Expected path parameters:', expectedParams); + debug(' Actual path parameters:', params); return expectedKeys.every(key => params[key] === expectedParams[key]); }; }; const getBodyMatcher = ({ body: expectedBody }) => { + debug('Generating body matcher'); return (url, { body, method = 'get' }) => { + debug('Attempting to match body'); if (method.toLowerCase() === 'get') { + debug(' GET request - skip matching body'); // GET requests don’t send a body so the body matcher should be ignored for them return true; } @@ -93,8 +128,13 @@ const getBodyMatcher = ({ body: expectedBody }) => { let sentBody; try { + debug(' Parsing request body as JSON'); sentBody = JSON.parse(body); - } catch (_) {} + } catch (err) { + debug(' Failed to parse request body as JSON', err); + } + debug('Expected body:', expectedBody); + debug('Actual body:', sentBody); return sentBody && isEqual(sentBody, expectedBody); }; @@ -105,39 +145,66 @@ const getFullUrlMatcher = (route, matcherUrl, query) => { // but we have to be careful to normalize the url we check and the name // of the route to allow for e.g. http://it.at.there being indistinguishable // from http://it.at.there/ once we start generating Request/Url objects + debug(' Matching using full url', matcherUrl); const expectedUrl = normalizeUrl(matcherUrl); + debug(' Normalised url to:', matcherUrl); if (route.identifier === matcherUrl) { + debug(' Updating route identifier to match normalized url:', matcherUrl); route.identifier = expectedUrl; } return matcherUrl => { + debug('Expected url:', expectedUrl); + debug('Actual url:', matcherUrl); if (query && expectedUrl.indexOf('?')) { + debug('Ignoring query string when matching url'); return matcherUrl.indexOf(expectedUrl) === 0; } return normalizeUrl(matcherUrl) === expectedUrl; }; }; -<<<<<<< HEAD -const getFunctionMatcher = ({ functionMatcher }) => functionMatcher; +const getFunctionMatcher = ({ matcher, functionMatcher }) => { + if (functionMatcher) { + debug('Using user defined function as matcher alongside other matchers'); + return functionMatcher; + } + if (typeof matcher === 'function') { + debug('Using user defined function as matcher'); + return matcher; + } +}; + +const getFunctionMatcher = ({ functionMatcher }) => { + debug('Detected user defined function matcher', functionMatcher); + return (...args) => { + debug('Calling function matcher with arguments', args) + return functionMatcher(...args); + } +} const getUrlMatcher = route => { + debug('Generating url matcher'); const { url: matcherUrl, query } = route; if (matcherUrl === '*') { + debug(' Using universal * rule to match any url'); return () => true; } if (matcherUrl instanceof RegExp) { + debug(' Using regular expression to match url:', matcherUrl); return url => matcherUrl.test(url); } if (matcherUrl.href) { + debug(` Using URL object to match url`, matcherUrl); return getFullUrlMatcher(route, matcherUrl.href, query); } for (const shorthand in stringMatchers) { if (matcherUrl.indexOf(shorthand + ':') === 0) { + debug(` Using ${shorthand}: pattern to match url`, matcherUrl); const urlFragment = matcherUrl.replace(new RegExp(`^${shorthand}:`), ''); return stringMatchers[shorthand](urlFragment); } @@ -146,8 +213,8 @@ const getUrlMatcher = route => { return getFullUrlMatcher(route, matcherUrl, query); }; -module.exports = route => { - debug('Generating matcher for route') +module.exports = (route, useDebugger = true) => { + useDebugger && debug('Compiling matcher for route'); const matchers = [ route.query && getQueryStringMatcher(route), route.method && getMethodMatcher(route), @@ -156,17 +223,6 @@ module.exports = route => { route.body && getBodyMatcher(route), route.functionMatcher && getFunctionMatcher(route), route.url && getUrlMatcher(route) -======= -module.exports = (route, useDebugger = true) => { - useDebugger && debug('Compiling matcher for route') - const matchers = [ - getQueryStringMatcher(route), - getMethodMatcher(route), - getHeaderMatcher(route), - getParamsMatcher(route), - getFunctionMatcher(route), - getUrlMatcher(route) ->>>>>>> minor refactor to make debug logging easier in matcher building ].filter(matcher => !!matcher); return (url, options = {}, request) => diff --git a/src/lib/inspecting.js b/src/lib/inspecting.js index e5d7dd59..8a5e6fbe 100644 --- a/src/lib/inspecting.js +++ b/src/lib/inspecting.js @@ -6,7 +6,10 @@ const isName = nameOrMatcher => typeof nameOrMatcher === 'string' && /^[\da-zA-Z\-]+$/.test(nameOrMatcher); const filterCallsWithMatcher = (matcher, options = {}, calls) => { - matcher = generateMatcher(sanitizeRoute(Object.assign({ matcher }, options), false), false); + matcher = generateMatcher( + sanitizeRoute(Object.assign({ matcher }, options), false), + false + ); return calls.filter(([url, options]) => matcher(normalizeUrl(url), options)); }; From 13b947570d7288e29d762b0bf42b9c79b417a115 Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sat, 25 Jan 2020 17:37:18 +0000 Subject: [PATCH 07/12] more debug logs --- README.md | 2 +- src/lib/compile-route.js | 27 +++++++++++++++++---------- src/lib/fetch-handler.js | 24 +++++++++++++++--------- src/lib/generate-matcher.js | 21 +++++---------------- src/lib/inspecting.js | 5 +---- src/lib/set-up-and-tear-down.js | 10 +++++++++- 6 files changed, 48 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index d14597cb..df202ecd 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Mock http requests made using [fetch](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch). -*New* If using jest, try the new [fetch-mock-jest](https://www.npmjs.com/package/fetch-mock-jest) wrapper. +_New_ If using jest, try the new [fetch-mock-jest](https://www.npmjs.com/package/fetch-mock-jest) wrapper. ![node version](https://img.shields.io/node/v/fetch-mock.svg?style=flat-square) [![licence](https://img.shields.io/npm/l/fetch-mock.svg?style=flat-square)](https://github.com/wheresrhys/fetch-mock/blob/master/LICENSE) diff --git a/src/lib/compile-route.js b/src/lib/compile-route.js index 50977b88..298e9377 100644 --- a/src/lib/compile-route.js +++ b/src/lib/compile-route.js @@ -36,12 +36,12 @@ const argsToRoute = args => { return routeConfig; }; -const sanitizeRoute = (route, useDebugger = true) => { - useDebugger && debug('Sanitizing route properties'); +const sanitizeRoute = route => { + debug('Sanitizing route properties'); route = Object.assign({}, route); if (route.method) { - useDebugger && debug(`Converting method ${route.method} to lower case`); + debug(`Converting method ${route.method} to lower case`); route.method = route.method.toLowerCase(); } if (isUrlMatcher(route.matcher)) { @@ -51,11 +51,12 @@ const sanitizeRoute = (route, useDebugger = true) => { route.functionMatcher = route.matcher || route.functionMatcher; - useDebugger && debug('Setting route.identifier...') - useDebugger && debug(` route.name is ${route.name}`) - useDebugger && debug(` route.matcher is ${route.matcher}`) + debug('Setting route.identifier...'); + debug(` route.name is ${route.name}`); + debug(` route.url is ${route.url}`); + debug(` route.functionMatcher is ${route.functionMatcher}`); route.identifier = route.name || route.url || route.functionMatcher; - useDebugger && debug(` > route.identifier set to ${route.identifier}`); + debug(` > route.identifier set to ${route.identifier}`); return route; }; @@ -74,7 +75,7 @@ const validateRoute = route => { const limitMatcher = route => { debug('Limiting number of requests to handle by route'); if (!route.repeat) { - debug('No `repeat` value set on route. Will match any number of requests'); + debug(' No `repeat` value set on route. Will match any number of requests'); return; } @@ -92,11 +93,17 @@ const limitMatcher = route => { }; const delayResponse = route => { + debug(`Delaying response`) const { delay } = route; if (delay) { + debug(` Wrapping response in delay of ${delay} miliseconds`) const response = route.response; - route.response = () => - new Promise(res => setTimeout(() => res(response), delay)); + route.response = () => { + debug(`Delaying response by ${delay} miliseconds`) + return new Promise(res => setTimeout(() => res(response), delay)); + } + } else { + debug(` No delay set on route. Will respond 'immediately' (but asynchronously)`) } }; diff --git a/src/lib/fetch-handler.js b/src/lib/fetch-handler.js index d55c2315..dbda73ab 100755 --- a/src/lib/fetch-handler.js +++ b/src/lib/fetch-handler.js @@ -45,8 +45,8 @@ const resolve = async ( debug(' > Calling fetch with url and options'); return response(url, options); } else { - debug(' > Calling custom matcher function'); - return response(url, options, request); + debug(' > Calling response function'); + response = response(url, options, request); } } else if (typeof response.then === 'function') { debug(' Response is a promise'); @@ -61,9 +61,8 @@ const resolve = async ( }; FetchMock.fetchHandler = function(url, options, request) { - debug('**HANDLING NEW FETCH**'); + debug('fetch called with:', url, options) const normalizedRequest = requestUtils.normalizeRequest( - ({ url, options, request } = requestUtils.normalizeRequest( url, options, this.config.Request @@ -83,7 +82,7 @@ FetchMock.fetchHandler = function(url, options, request) { // constructors defined by the user return new this.config.Promise((res, rej) => { if (signal) { - debug('options.signal exists - setting up fetch aborting') + debug('options.signal exists - setting up fetch aborting'); const abort = () => { // note that DOMException is not available in node.js; even node-fetch uses a custom error class: https://github.com/bitinn/node-fetch/blob/master/src/abort-error.js rej( @@ -94,7 +93,7 @@ FetchMock.fetchHandler = function(url, options, request) { done(); }; if (signal.aborted) { - debug('options.signal is already aborted- abort the fetch') + debug('options.signal is already aborted- abort the fetch'); abort(); } signal.addEventListener('abort', abort); @@ -102,14 +101,18 @@ FetchMock.fetchHandler = function(url, options, request) { this.generateResponse(route, url, options, request) .then(res, rej) - .then(done, done); + .then(done, done) + .then(() => { + debug('fetch handled successfully') + debug('---------------') + }) }); }; FetchMock.fetchHandler.isMock = true; FetchMock.executeRouter = function(url, options, request) { - debug('Attempting to match request to defined routes'); + debug(`Attempting to match request to a route`); if (this.config.fallbackToNetwork === 'always') { debug( ' Configured with fallbackToNetwork=always - passing through to fetch' @@ -173,7 +176,10 @@ FetchMock.generateResponse = async function(route, url, options, request) { }; FetchMock.router = function(url, options, request) { - const route = this.routes.find(route => route.matcher(url, options, request)); + const route = this.routes.find((route, i) => { + debug(`Trying to match route ${i}`) + return route.matcher(url, options, request) + }); if (route) { this.push({ diff --git a/src/lib/generate-matcher.js b/src/lib/generate-matcher.js index 1e351375..180a11df 100644 --- a/src/lib/generate-matcher.js +++ b/src/lib/generate-matcher.js @@ -164,24 +164,13 @@ const getFullUrlMatcher = (route, matcherUrl, query) => { }; }; -const getFunctionMatcher = ({ matcher, functionMatcher }) => { - if (functionMatcher) { - debug('Using user defined function as matcher alongside other matchers'); - return functionMatcher; - } - if (typeof matcher === 'function') { - debug('Using user defined function as matcher'); - return matcher; - } -}; - const getFunctionMatcher = ({ functionMatcher }) => { debug('Detected user defined function matcher', functionMatcher); return (...args) => { - debug('Calling function matcher with arguments', args) + debug('Calling function matcher with arguments', args); return functionMatcher(...args); - } -} + }; +}; const getUrlMatcher = route => { debug('Generating url matcher'); @@ -213,8 +202,8 @@ const getUrlMatcher = route => { return getFullUrlMatcher(route, matcherUrl, query); }; -module.exports = (route, useDebugger = true) => { - useDebugger && debug('Compiling matcher for route'); +module.exports = route => { + debug('Compiling matcher for route'); const matchers = [ route.query && getQueryStringMatcher(route), route.method && getMethodMatcher(route), diff --git a/src/lib/inspecting.js b/src/lib/inspecting.js index 8a5e6fbe..65baed9c 100644 --- a/src/lib/inspecting.js +++ b/src/lib/inspecting.js @@ -6,10 +6,7 @@ const isName = nameOrMatcher => typeof nameOrMatcher === 'string' && /^[\da-zA-Z\-]+$/.test(nameOrMatcher); const filterCallsWithMatcher = (matcher, options = {}, calls) => { - matcher = generateMatcher( - sanitizeRoute(Object.assign({ matcher }, options), false), - false - ); + matcher = generateMatcher(sanitizeRoute(Object.assign({ matcher }, options))); return calls.filter(([url, options]) => matcher(normalizeUrl(url), options)); }; diff --git a/src/lib/set-up-and-tear-down.js b/src/lib/set-up-and-tear-down.js index d880ffb5..9c010d9c 100644 --- a/src/lib/set-up-and-tear-down.js +++ b/src/lib/set-up-and-tear-down.js @@ -1,3 +1,4 @@ +const debug = require('debug')('fetch-mock'); const { compileRoute } = require('./compile-route'); const FetchMock = {}; @@ -10,6 +11,7 @@ FetchMock.mock = function(...args) { }; FetchMock.addRoute = function(uncompiledRoute) { + debug('Adding route', uncompiledRoute) const route = this.compileRoute(uncompiledRoute); const clashes = this.routes.filter( ({ identifier, method }) => @@ -24,6 +26,8 @@ FetchMock.addRoute = function(uncompiledRoute) { if (overwriteRoutes === false || !clashes.length) { this._uncompiledRoutes.push(uncompiledRoute); + debug('Route added successfully') + debug('---------------') return this.routes.push(route); } @@ -33,7 +37,8 @@ FetchMock.addRoute = function(uncompiledRoute) { this._uncompiledRoutes.splice(index, 1, uncompiledRoute); this.routes.splice(index, 1, route); }); - + debug('Route added successfully') + debug('---------------') return this.routes; } @@ -45,6 +50,9 @@ FetchMock.addRoute = function(uncompiledRoute) { this._uncompiledRoutes.push(uncompiledRoute); this.routes.push(route); + debug('Route added successfully') + debug('---------------') + }; FetchMock._mock = function() { From 51a7d6094c9da357a9f9d7b010c42b76b245ba3b Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sat, 25 Jan 2020 21:14:13 +0000 Subject: [PATCH 08/12] most debug messages are done --- src/lib/compile-route.js | 12 ++-- src/lib/debug.js | 24 ++++++++ src/lib/fetch-handler.js | 30 ++++++---- src/lib/generate-matcher.js | 3 +- src/lib/index.js | 3 + src/lib/inspecting.js | 99 +++++++++++++++++++++++++-------- src/lib/request-utils.js | 1 + src/lib/response-builder.js | 1 + src/lib/set-up-and-tear-down.js | 10 +--- 9 files changed, 136 insertions(+), 47 deletions(-) create mode 100644 src/lib/debug.js diff --git a/src/lib/compile-route.js b/src/lib/compile-route.js index 298e9377..630deec2 100644 --- a/src/lib/compile-route.js +++ b/src/lib/compile-route.js @@ -1,4 +1,4 @@ -const debug = require('debug')('fetch-mock'); +const {debug, setDebugNamespace} = require('./debug'); const generateMatcher = require('./generate-matcher'); const matcherProperties = [ @@ -15,6 +15,7 @@ const isUrlMatcher = matcher => matcher instanceof RegExp || typeof matcher === 'string' || (typeof matcher === 'object' && 'href' in matcher); + const isFunctionMatcher = matcher => typeof matcher === 'function'; const argsToRoute = args => { @@ -45,6 +46,7 @@ const sanitizeRoute = route => { route.method = route.method.toLowerCase(); } if (isUrlMatcher(route.matcher)) { + debug('Mock uses a url matcher', route.matcher) route.url = route.matcher; delete route.matcher; } @@ -56,7 +58,7 @@ const sanitizeRoute = route => { debug(` route.url is ${route.url}`); debug(` route.functionMatcher is ${route.functionMatcher}`); route.identifier = route.name || route.url || route.functionMatcher; - debug(` > route.identifier set to ${route.identifier}`); + debug(` -> route.identifier set to ${route.identifier}`); return route; }; @@ -79,7 +81,7 @@ const limitMatcher = route => { return; } - debug(`Route set to repeat ${route.repeat} times`); + debug(` Route set to repeat ${route.repeat} times`); const matcher = route.matcher; let timesLeft = route.repeat; route.matcher = (url, options) => { @@ -93,7 +95,7 @@ const limitMatcher = route => { }; const delayResponse = route => { - debug(`Delaying response`) + debug(`Applying response delay settings`) const { delay } = route; if (delay) { debug(` Wrapping response in delay of ${delay} miliseconds`) @@ -108,12 +110,14 @@ const delayResponse = route => { }; const compileRoute = function(args) { + setDebugNamespace('compile'); debug('Compiling route'); const route = sanitizeRoute(argsToRoute(args)); validateRoute(route); route.matcher = generateMatcher(route); limitMatcher(route); delayResponse(route); + setDebugNamespace(); return route; }; diff --git a/src/lib/debug.js b/src/lib/debug.js new file mode 100644 index 00000000..4f4db385 --- /dev/null +++ b/src/lib/debug.js @@ -0,0 +1,24 @@ +const debug = require('debug'); + +let debugFunc +let phase = 'default'; +let namespace = ''; +const newDebug = () => { + debugFunc = namespace ? debug(`fetch-mock:${phase}:${namespace}`) : debug(`fetch-mock:${phase}`) +} + +newDebug() + +module.exports = { + debug: (...args) => { + debugFunc(...args) + }, + setDebugNamespace: str => { + namespace = str + newDebug(); + }, + setDebugPhase: str => { + phase = str || 'default'; + newDebug(); + } +} diff --git a/src/lib/fetch-handler.js b/src/lib/fetch-handler.js index dbda73ab..f566e32d 100755 --- a/src/lib/fetch-handler.js +++ b/src/lib/fetch-handler.js @@ -1,4 +1,4 @@ -const debug = require('debug')('fetch-mock'); +const {debug, setDebugPhase} = require('./debug'); const responseBuilder = require('./response-builder'); const requestUtils = require('./request-utils'); const FetchMock = {}; @@ -39,28 +39,29 @@ const resolve = async ( // the original Request instance, not our normalised url + options if (responseIsFetch) { if (request) { - debug(' > Calling fetch with Request instance'); + debug(' -> Calling fetch with Request instance'); return response(request); } - debug(' > Calling fetch with url and options'); + debug(' -> Calling fetch with url and options'); return response(url, options); } else { - debug(' > Calling response function'); + debug(' -> Calling response function'); response = response(url, options, request); } } else if (typeof response.then === 'function') { debug(' Response is a promise'); - debug(' > Resolving promise'); + debug(' -> Resolving promise'); response = await response; } else { debug(' Response is not a function or a promise'); - debug(' > Returning response for conversion into Response instance'); + debug(' -> Exiting response resolution recursion'); return response; } } }; FetchMock.fetchHandler = function(url, options, request) { + setDebugPhase('handle'); debug('fetch called with:', url, options) const normalizedRequest = requestUtils.normalizeRequest( url, @@ -72,6 +73,12 @@ FetchMock.fetchHandler = function(url, options, request) { const { signal } = normalizedRequest; + debug('Request normalised') + debug(' url', url) + debug(' options', options) + debug(' request', request) + debug(' signal', signal) + const route = this.executeRouter(url, options, request); // this is used to power the .flush() method @@ -82,8 +89,9 @@ FetchMock.fetchHandler = function(url, options, request) { // constructors defined by the user return new this.config.Promise((res, rej) => { if (signal) { - debug('options.signal exists - setting up fetch aborting'); + debug('signal exists - enabling fetch abort'); const abort = () => { + debug('aborting fetch'); // note that DOMException is not available in node.js; even node-fetch uses a custom error class: https://github.com/bitinn/node-fetch/blob/master/src/abort-error.js rej( typeof DOMException !== 'undefined' @@ -93,7 +101,7 @@ FetchMock.fetchHandler = function(url, options, request) { done(); }; if (signal.aborted) { - debug('options.signal is already aborted- abort the fetch'); + debug('signal is already aborted - aborting the fetch'); abort(); } signal.addEventListener('abort', abort); @@ -103,8 +111,7 @@ FetchMock.fetchHandler = function(url, options, request) { .then(res, rej) .then(done, done) .then(() => { - debug('fetch handled successfully') - debug('---------------') + setDebugPhase(); }) }); }; @@ -196,13 +203,14 @@ FetchMock.getNativeFetch = function() { const func = this.realFetch || (this.isSandbox && this.config.fetch); if (!func) { throw new Error( - 'fetch-mock: Falling back to network only available on gloabl fetch-mock, or by setting config.fetch on sandboxed fetch-mock' + 'fetch-mock: Falling back to network only available on global fetch-mock, or by setting config.fetch on sandboxed fetch-mock' ); } return func; }; FetchMock.push = function({ url, options, request, isUnmatched, identifier }) { + debug('Recording fetch call', { url, options, request, isUnmatched, identifier }) const args = [url, options]; args.request = request; args.identifier = identifier; diff --git a/src/lib/generate-matcher.js b/src/lib/generate-matcher.js index 180a11df..caef1d0a 100644 --- a/src/lib/generate-matcher.js +++ b/src/lib/generate-matcher.js @@ -1,4 +1,4 @@ -const debug = require('debug')('fetch-mock'); +const {debug} = require('./debug'); const glob = require('glob-to-regexp'); const pathToRegexp = require('path-to-regexp'); const querystring = require('querystring'); @@ -214,6 +214,7 @@ module.exports = route => { route.url && getUrlMatcher(route) ].filter(matcher => !!matcher); + debug('Compiled matcher for route'); return (url, options = {}, request) => matchers.every(matcher => matcher(url, options, request)); }; diff --git a/src/lib/index.js b/src/lib/index.js index 44153747..601e8c87 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -1,3 +1,4 @@ +const {debug} = require('./debug'); const setUpAndTearDown = require('./set-up-and-tear-down'); const fetchHandler = require('./fetch-handler'); const inspecting = require('./inspecting'); @@ -13,6 +14,7 @@ FetchMock.config = { }; FetchMock.createInstance = function() { + debug('Creating fetch-mock instance') const instance = Object.create(FetchMock); instance._uncompiledRoutes = (this._uncompiledRoutes || []).slice(); instance.routes = instance._uncompiledRoutes.map(config => @@ -34,6 +36,7 @@ FetchMock.bindMethods = function() { }; FetchMock.sandbox = function() { + debug('Creating sandboxed fetch-mock instance') // this construct allows us to create a fetch-mock instance which is also // a callable function, while circumventing circularity when defining the // object that this function should be bound to diff --git a/src/lib/inspecting.js b/src/lib/inspecting.js index 65baed9c..3ab95a2a 100644 --- a/src/lib/inspecting.js +++ b/src/lib/inspecting.js @@ -1,3 +1,4 @@ +const {setDebugPhase, setDebugNamespace, debug} = require('./debug'); const { normalizeUrl } = require('./request-utils'); const FetchMock = {}; const { sanitizeRoute } = require('./compile-route'); @@ -10,76 +11,118 @@ const filterCallsWithMatcher = (matcher, options = {}, calls) => { return calls.filter(([url, options]) => matcher(normalizeUrl(url), options)); }; +const formatDebug = (func) => { + return function (...args) { + setDebugPhase('inspect') + const result = func.call(this, ...args); + setDebugPhase() + return result; + } +} + FetchMock.filterCalls = function(nameOrMatcher, options) { + debug('Filtering fetch calls') let calls = this._calls; let matcher = '*'; if ([true, 'matched'].includes(nameOrMatcher)) { + debug(`Filter provided is ${nameOrMatcher}. Returning matched calls only`) calls = calls.filter(({ isUnmatched }) => !isUnmatched); + } else if ([false, 'unmatched'].includes(nameOrMatcher)) { + debug(`Filter provided is ${nameOrMatcher}. Returning unmatched calls only`) calls = calls.filter(({ isUnmatched }) => isUnmatched); } else if (typeof nameOrMatcher === 'undefined') { + debug(`Filter provided is undefined. Returning all calls`) calls = calls; } else if (isName(nameOrMatcher)) { + debug(`Filter provided, looks like the name of a named route. Returning only calls handled by that route`) calls = calls.filter(({ identifier }) => identifier === nameOrMatcher); } else { matcher = normalizeUrl(nameOrMatcher); if (this.routes.some(({ identifier }) => identifier === matcher)) { + debug(`Filter provided, ${nameOrMatcher}, identifies a route. Returning only calls handled by that route`) calls = calls.filter(call => call.identifier === matcher); } } + if ((options || matcher !== '*') && calls.length) { if (typeof options === 'string') { options = { method: options }; } + debug('Compiling filter and options to route in order to filter all calls', nameOrMatcher) calls = filterCallsWithMatcher(matcher, options, calls); } + debug(`Retrieved ${calls.length} calls`) return calls; }; -FetchMock.calls = function(nameOrMatcher, options) { +FetchMock.calls = formatDebug(function(nameOrMatcher, options) { + debug('retrieving matching calls'); return this.filterCalls(nameOrMatcher, options); -}; +}); -FetchMock.lastCall = function(nameOrMatcher, options) { +FetchMock.lastCall = formatDebug(function(nameOrMatcher, options) { + debug('retrieving last matching call'); return [...this.filterCalls(nameOrMatcher, options)].pop(); -}; +}); -FetchMock.lastUrl = function(nameOrMatcher, options) { +FetchMock.lastUrl = formatDebug(function(nameOrMatcher, options) { + debug('retrieving url of last matching call'); return (this.lastCall(nameOrMatcher, options) || [])[0]; -}; +}); -FetchMock.lastOptions = function(nameOrMatcher, options) { +FetchMock.lastOptions = formatDebug(function(nameOrMatcher, options) { + debug('retrieving options of last matching call'); return (this.lastCall(nameOrMatcher, options) || [])[1]; -}; +}); -FetchMock.called = function(nameOrMatcher, options) { +FetchMock.called = formatDebug(function(nameOrMatcher, options) { + debug('checking if matching call was made'); return !!this.filterCalls(nameOrMatcher, options).length; -}; +}); + +FetchMock.flush = formatDebug(async function(waitForResponseMethods) { + setDebugNamespace('flush') + debug(`flushing all fetch calls. ${waitForResponseMethods ? '' : 'Not '}waiting for response bodies to complete download`); -FetchMock.flush = function(waitForResponseMethods) { const queuedPromises = this._holdingPromises; this._holdingPromises = []; - - return Promise.all(queuedPromises).then(() => { - if (waitForResponseMethods && this._holdingPromises.length) { - return this.flush(waitForResponseMethods); + debug(`${queuedPromises.length} fetch calls to be awaited`) + + await Promise.all(queuedPromises) + debug(`All fetch calls have completed`) + if (waitForResponseMethods && this._holdingPromises.length) { + debug(`Awaiting all fetch bodies to download`) + await this.flush(waitForResponseMethods); + debug(`All fetch bodies have completed downloading`) + } + setDebugNamespace() +}); + +FetchMock.done = formatDebug(function(nameOrMatcher) { + setDebugPhase('inspect') + setDebugNamespace('done') + debug('Checking to see if expected calls have been made') + let routesToCheck; + + + if(nameOrMatcher && typeof nameOrMatcher !== 'boolean') { + debug('Checking to see if expected calls have been made for single route:', nameOrMatcher) + routesToCheck = [{ identifier: nameOrMatcher }] + } else { + debug('Checking to see if expected calls have been made for all routes') + routesToCheck = this.routes; } - }); -}; -FetchMock.done = function(nameOrMatcher) { - const routesToCheck = - nameOrMatcher && typeof nameOrMatcher !== 'boolean' - ? [{ identifier: nameOrMatcher }] - : this.routes; // Can't use array.every because would exit after first failure, which would // break the logging - return routesToCheck + const result = routesToCheck .map(({ identifier }) => { if (!this.called(identifier)) { + debug('No calls made for route:', identifier) console.warn(`Warning: ${identifier} not called`); // eslint-disable-line return false; } @@ -89,10 +132,14 @@ FetchMock.done = function(nameOrMatcher) { ).repeat; if (!expectedTimes) { + debug('Route has been called at least once, and no expectation of more set:', identifier) return true; } const actualTimes = this.filterCalls(identifier).length; + + debug(`Route called ${actualTimes} times:`, identifier) if (expectedTimes > actualTimes) { + debug(`Route called ${actualTimes} times, but expected ${expectedTimes}:`, identifier) console.warn( `Warning: ${identifier} only called ${actualTimes} times, but ${expectedTimes} expected` ); // eslint-disable-line @@ -102,6 +149,10 @@ FetchMock.done = function(nameOrMatcher) { } }) .every(isDone => isDone); -}; + + setDebugNamespace() + setDebugPhase() + return result +}); module.exports = FetchMock; diff --git a/src/lib/request-utils.js b/src/lib/request-utils.js index 319c00f2..9dd570fe 100644 --- a/src/lib/request-utils.js +++ b/src/lib/request-utils.js @@ -1,3 +1,4 @@ +const {debug} = require('./debug'); let URL; // https://stackoverflow.com/a/19709846/308237 const absoluteUrlRX = new RegExp('^(?:[a-z]+:)?//', 'i'); diff --git a/src/lib/response-builder.js b/src/lib/response-builder.js index e9482b24..24f884b1 100644 --- a/src/lib/response-builder.js +++ b/src/lib/response-builder.js @@ -1,3 +1,4 @@ +const {debug} = require('./debug'); const responseConfigProps = [ 'body', 'headers', diff --git a/src/lib/set-up-and-tear-down.js b/src/lib/set-up-and-tear-down.js index 9c010d9c..ddfbc3ae 100644 --- a/src/lib/set-up-and-tear-down.js +++ b/src/lib/set-up-and-tear-down.js @@ -1,8 +1,9 @@ -const debug = require('debug')('fetch-mock'); +const {debug, setDebugPhase} = require('./debug'); const { compileRoute } = require('./compile-route'); const FetchMock = {}; FetchMock.mock = function(...args) { + setDebugPhase('setup'); if (args.length) { this.addRoute(args); } @@ -26,8 +27,6 @@ FetchMock.addRoute = function(uncompiledRoute) { if (overwriteRoutes === false || !clashes.length) { this._uncompiledRoutes.push(uncompiledRoute); - debug('Route added successfully') - debug('---------------') return this.routes.push(route); } @@ -37,8 +36,6 @@ FetchMock.addRoute = function(uncompiledRoute) { this._uncompiledRoutes.splice(index, 1, uncompiledRoute); this.routes.splice(index, 1, route); }); - debug('Route added successfully') - debug('---------------') return this.routes; } @@ -50,8 +47,6 @@ FetchMock.addRoute = function(uncompiledRoute) { this._uncompiledRoutes.push(uncompiledRoute); this.routes.push(route); - debug('Route added successfully') - debug('---------------') }; @@ -61,6 +56,7 @@ FetchMock._mock = function() { this.realFetch = this.realFetch || this.global.fetch; this.global.fetch = this.fetchHandler; } + setDebugPhase(); return this; }; From 8c2f83379cd4e6b641712c06f01c950e68ee9260 Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sat, 25 Jan 2020 21:16:44 +0000 Subject: [PATCH 09/12] most debug messages are done --- src/lib/compile-route.js | 10 +++++++--- src/lib/debug.js | 7 ++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/lib/compile-route.js b/src/lib/compile-route.js index 630deec2..8d543323 100644 --- a/src/lib/compile-route.js +++ b/src/lib/compile-route.js @@ -1,4 +1,4 @@ -const {debug, setDebugNamespace} = require('./debug'); +const {debug, getDebug, setDebugNamespace} = require('./debug'); const generateMatcher = require('./generate-matcher'); const matcherProperties = [ @@ -38,6 +38,7 @@ const argsToRoute = args => { }; const sanitizeRoute = route => { + const debug = getDebug('sanitizeRoute()'); debug('Sanitizing route properties'); route = Object.assign({}, route); @@ -74,7 +75,8 @@ const validateRoute = route => { } }; -const limitMatcher = route => { +const limit = route => { + const debug = getDebug('limit()'); debug('Limiting number of requests to handle by route'); if (!route.repeat) { debug(' No `repeat` value set on route. Will match any number of requests'); @@ -95,6 +97,7 @@ const limitMatcher = route => { }; const delayResponse = route => { + const debug = getDebug('delayResponse()'); debug(`Applying response delay settings`) const { delay } = route; if (delay) { @@ -110,12 +113,13 @@ const delayResponse = route => { }; const compileRoute = function(args) { + const debug = getDebug('compileRoute()'); setDebugNamespace('compile'); debug('Compiling route'); const route = sanitizeRoute(argsToRoute(args)); validateRoute(route); route.matcher = generateMatcher(route); - limitMatcher(route); + limit(route); delayResponse(route); setDebugNamespace(); return route; diff --git a/src/lib/debug.js b/src/lib/debug.js index 4f4db385..4d5e5eab 100644 --- a/src/lib/debug.js +++ b/src/lib/debug.js @@ -3,8 +3,8 @@ const debug = require('debug'); let debugFunc let phase = 'default'; let namespace = ''; -const newDebug = () => { - debugFunc = namespace ? debug(`fetch-mock:${phase}:${namespace}`) : debug(`fetch-mock:${phase}`) +const newDebug = (ns) => { + debugFunc = (ns || namespace) ? debug(`fetch-mock:${phase}:${namespace}`) : debug(`fetch-mock:${phase}`) } newDebug() @@ -20,5 +20,6 @@ module.exports = { setDebugPhase: str => { phase = str || 'default'; newDebug(); - } + }, + getDebug = (namespace) => newDebug(namespace); } From 671130b614da253f60c21640c2dc07884bd52411 Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sat, 25 Jan 2020 21:31:39 +0000 Subject: [PATCH 10/12] finalised debug logs --- src/lib/compile-route.js | 2 -- src/lib/debug.js | 8 +++++--- src/lib/fetch-handler.js | 6 +++++- src/lib/generate-matcher.js | 4 +++- src/lib/response-builder.js | 17 ++++++++++++++++- 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/lib/compile-route.js b/src/lib/compile-route.js index 8d543323..0a13f8a7 100644 --- a/src/lib/compile-route.js +++ b/src/lib/compile-route.js @@ -114,14 +114,12 @@ const delayResponse = route => { const compileRoute = function(args) { const debug = getDebug('compileRoute()'); - setDebugNamespace('compile'); debug('Compiling route'); const route = sanitizeRoute(argsToRoute(args)); validateRoute(route); route.matcher = generateMatcher(route); limit(route); delayResponse(route); - setDebugNamespace(); return route; }; diff --git a/src/lib/debug.js b/src/lib/debug.js index 4d5e5eab..1ff215fd 100644 --- a/src/lib/debug.js +++ b/src/lib/debug.js @@ -3,10 +3,12 @@ const debug = require('debug'); let debugFunc let phase = 'default'; let namespace = ''; -const newDebug = (ns) => { - debugFunc = (ns || namespace) ? debug(`fetch-mock:${phase}:${namespace}`) : debug(`fetch-mock:${phase}`) +const newDebug = () => { + debugFunc = namespace ? debug(`fetch-mock:${phase}:${namespace}`) : debug(`fetch-mock:${phase}`) } +const newDebugSandbox = (ns) => debug(`fetch-mock:${phase}:${ns}`); + newDebug() module.exports = { @@ -21,5 +23,5 @@ module.exports = { phase = str || 'default'; newDebug(); }, - getDebug = (namespace) => newDebug(namespace); + getDebug: (namespace) => newDebugSandbox(namespace) } diff --git a/src/lib/fetch-handler.js b/src/lib/fetch-handler.js index f566e32d..ee00cd92 100755 --- a/src/lib/fetch-handler.js +++ b/src/lib/fetch-handler.js @@ -1,4 +1,4 @@ -const {debug, setDebugPhase} = require('./debug'); +const {debug, setDebugPhase, getDebug} = require('./debug'); const responseBuilder = require('./response-builder'); const requestUtils = require('./request-utils'); const FetchMock = {}; @@ -24,6 +24,7 @@ const resolve = async ( options, request ) => { + const debug = getDebug('resolve()'); debug('Recursively resolving function and promise responses'); // We want to allow things like // - function returning a Promise for a response @@ -62,6 +63,7 @@ const resolve = async ( FetchMock.fetchHandler = function(url, options, request) { setDebugPhase('handle'); + const debug = getDebug('fetchHandler()'); debug('fetch called with:', url, options) const normalizedRequest = requestUtils.normalizeRequest( url, @@ -119,6 +121,7 @@ FetchMock.fetchHandler = function(url, options, request) { FetchMock.fetchHandler.isMock = true; FetchMock.executeRouter = function(url, options, request) { + const debug = getDebug('executeRouter()'); debug(`Attempting to match request to a route`); if (this.config.fallbackToNetwork === 'always') { debug( @@ -158,6 +161,7 @@ FetchMock.executeRouter = function(url, options, request) { }; FetchMock.generateResponse = async function(route, url, options, request) { + const debug = getDebug('generateResponse()'); const response = await resolve(route, url, options, request); // If the response says to throw an error, throw it diff --git a/src/lib/generate-matcher.js b/src/lib/generate-matcher.js index caef1d0a..3bf8588f 100644 --- a/src/lib/generate-matcher.js +++ b/src/lib/generate-matcher.js @@ -1,4 +1,4 @@ -const {debug} = require('./debug'); +const {debug, getDebug, setDebugNamespace} = require('./debug'); const glob = require('glob-to-regexp'); const pathToRegexp = require('path-to-regexp'); const querystring = require('querystring'); @@ -203,6 +203,7 @@ const getUrlMatcher = route => { }; module.exports = route => { + setDebugNamespace('generateMatcher()') debug('Compiling matcher for route'); const matchers = [ route.query && getQueryStringMatcher(route), @@ -215,6 +216,7 @@ module.exports = route => { ].filter(matcher => !!matcher); debug('Compiled matcher for route'); + setDebugNamespace() return (url, options = {}, request) => matchers.every(matcher => matcher(url, options, request)); }; diff --git a/src/lib/response-builder.js b/src/lib/response-builder.js index 24f884b1..ddb38b84 100644 --- a/src/lib/response-builder.js +++ b/src/lib/response-builder.js @@ -1,4 +1,4 @@ -const {debug} = require('./debug'); +const {debug, getDebug} = require('./debug'); const responseConfigProps = [ 'body', 'headers', @@ -9,10 +9,13 @@ const responseConfigProps = [ class ResponseBuilder { constructor(options) { + this.debug = getDebug('ResponseBuilder()'); + this.debug('Response builder created with options', options) Object.assign(this, options); } exec() { + this.debug('building response') this.normalizeResponseConfig(); this.constructFetchOpts(); this.constructResponseBody(); @@ -40,12 +43,14 @@ class ResponseBuilder { normalizeResponseConfig() { // If the response config looks like a status, start to generate a simple response if (typeof this.responseConfig === 'number') { + this.debug('building response using status', this.responseConfig) this.responseConfig = { status: this.responseConfig }; // If the response config is not an object, or is an object that doesn't use // any reserved properties, assume it is meant to be the body of the response } else if (typeof this.responseConfig === 'string' || this.sendAsObject()) { + this.debug('building text response from', this.responseConfig) this.responseConfig = { body: this.responseConfig }; @@ -54,6 +59,7 @@ class ResponseBuilder { validateStatus(status) { if (!status) { + this.debug('No status provided. Defaulting to 200') return 200; } @@ -63,6 +69,7 @@ class ResponseBuilder { status >= 200) || status < 600 ) { + this.debug('Valid status provided', status) return status; } @@ -97,6 +104,7 @@ e.g. {"body": {"status: "registered"}}`); this.responseConfig.body != null && //eslint-disable-line typeof this.body === 'object' ) { + this.debug('Stringifying JSON response body') this.body = JSON.stringify(this.body); if (!this.options.headers.has('Content-Type')) { this.options.headers.set('Content-Type', 'application/json'); @@ -111,6 +119,7 @@ e.g. {"body": {"status: "registered"}}`); typeof this.body === 'string' && !this.options.headers.has('Content-Length') ) { + this.debug('Setting content-length header:', this.body.length.toString()) this.options.headers.set('Content-Length', this.body.length.toString()); } } @@ -124,6 +133,7 @@ e.g. {"body": {"status: "registered"}}`); // On the server we need to manually construct the readable stream for the // Response object (on the client this done automatically) if (this.Stream) { + this.debug('Creating response stream') const stream = new this.Stream.Readable(); if (this.body != null) { //eslint-disable-line stream.push(this.body, 'utf-8'); @@ -140,21 +150,26 @@ e.g. {"body": {"status: "registered"}}`); // Using a proxy means we can set properties that may not be writable on // the original Response. It also means we can track the resolution of // promises returned by res.json(), res.text() etc + this.debug('Wrappipng Response in ES proxy for observability') return new Proxy(response, { get: (originalResponse, name) => { if (this.responseConfig.redirectUrl) { if (name === 'url') { + this.debug('Retrieving redirect url', this.responseConfig.redirectUrl) return this.responseConfig.redirectUrl; } if (name === 'redirected') { + this.debug('Retrieving redirected status', true) return true; } } if (typeof originalResponse[name] === 'function') { + this.debug('Wrapping body promises in ES proxies for observability') return new Proxy(originalResponse[name], { apply: (func, thisArg, args) => { + this.debug(`Calling res.${name}`) const result = func.apply(response, args); if (result.then) { fetchMock._holdingPromises.push(result.catch(() => null)); From a575bb84ef339d7e4befa4cbae14cdaaedaf450c Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sat, 25 Jan 2020 21:35:09 +0000 Subject: [PATCH 11/12] docs --- docs/_troubleshooting/troubleshooting.md | 2 ++ docs/_usage/_defaults.md | 16 ---------------- docs/_usage/debug-mode.md | 6 ++++++ 3 files changed, 8 insertions(+), 16 deletions(-) delete mode 100644 docs/_usage/_defaults.md create mode 100644 docs/_usage/debug-mode.md diff --git a/docs/_troubleshooting/troubleshooting.md b/docs/_troubleshooting/troubleshooting.md index 2342334f..7f59edff 100644 --- a/docs/_troubleshooting/troubleshooting.md +++ b/docs/_troubleshooting/troubleshooting.md @@ -2,6 +2,8 @@ title: General position: 1 content_markdown: |- + The first step when debugging tests should be to run with the environment variable `DEBUG=fetch-mock*`. This will output additional logs for debugging purposes. + ### `fetch` is assigned to a local variable, not a global First of all, consider whether you could just use `fetch` as a global. Here are 3 reasons why this is a good idea: diff --git a/docs/_usage/_defaults.md b/docs/_usage/_defaults.md deleted file mode 100644 index 4c3e1fc8..00000000 --- a/docs/_usage/_defaults.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: -position: -parameters: - - name: - content: -content_markdown: -left_code_blocks: - - code_block: - title: - language: -right_code_blocks: - - code_block: - title: - language: ---- diff --git a/docs/_usage/debug-mode.md b/docs/_usage/debug-mode.md new file mode 100644 index 00000000..683f971f --- /dev/null +++ b/docs/_usage/debug-mode.md @@ -0,0 +1,6 @@ +--- +title: Debugging +position: 8 +content_markdown: |- + The first step when debugging tests should be to run with the environment variable `DEBUG=fetch-mock*`. This will output additional logs for debugging purposes. +--- From e6dff3258290b250f31538c5c1e5e79ddb0f9d7e Mon Sep 17 00:00:00 2001 From: Rhys Evans Date: Sat, 25 Jan 2020 21:36:57 +0000 Subject: [PATCH 12/12] lint --- src/lib/compile-route.js | 20 +++--- src/lib/debug.js | 20 +++--- src/lib/fetch-handler.js | 30 +++++---- src/lib/generate-matcher.js | 6 +- src/lib/index.js | 6 +- src/lib/inspecting.js | 106 +++++++++++++++++++------------- src/lib/request-utils.js | 1 - src/lib/response-builder.js | 33 +++++----- src/lib/set-up-and-tear-down.js | 5 +- 9 files changed, 129 insertions(+), 98 deletions(-) diff --git a/src/lib/compile-route.js b/src/lib/compile-route.js index 0a13f8a7..8a87d7f0 100644 --- a/src/lib/compile-route.js +++ b/src/lib/compile-route.js @@ -1,4 +1,4 @@ -const {debug, getDebug, setDebugNamespace} = require('./debug'); +const { getDebug } = require('./debug'); const generateMatcher = require('./generate-matcher'); const matcherProperties = [ @@ -47,7 +47,7 @@ const sanitizeRoute = route => { route.method = route.method.toLowerCase(); } if (isUrlMatcher(route.matcher)) { - debug('Mock uses a url matcher', route.matcher) + debug('Mock uses a url matcher', route.matcher); route.url = route.matcher; delete route.matcher; } @@ -79,7 +79,9 @@ const limit = route => { const debug = getDebug('limit()'); debug('Limiting number of requests to handle by route'); if (!route.repeat) { - debug(' No `repeat` value set on route. Will match any number of requests'); + debug( + ' No `repeat` value set on route. Will match any number of requests' + ); return; } @@ -98,17 +100,19 @@ const limit = route => { const delayResponse = route => { const debug = getDebug('delayResponse()'); - debug(`Applying response delay settings`) + debug(`Applying response delay settings`); const { delay } = route; if (delay) { - debug(` Wrapping response in delay of ${delay} miliseconds`) + debug(` Wrapping response in delay of ${delay} miliseconds`); const response = route.response; route.response = () => { - debug(`Delaying response by ${delay} miliseconds`) + debug(`Delaying response by ${delay} miliseconds`); return new Promise(res => setTimeout(() => res(response), delay)); - } + }; } else { - debug(` No delay set on route. Will respond 'immediately' (but asynchronously)`) + debug( + ` No delay set on route. Will respond 'immediately' (but asynchronously)` + ); } }; diff --git a/src/lib/debug.js b/src/lib/debug.js index 1ff215fd..c6c06f7a 100644 --- a/src/lib/debug.js +++ b/src/lib/debug.js @@ -1,27 +1,29 @@ const debug = require('debug'); -let debugFunc +let debugFunc; let phase = 'default'; let namespace = ''; const newDebug = () => { - debugFunc = namespace ? debug(`fetch-mock:${phase}:${namespace}`) : debug(`fetch-mock:${phase}`) -} + debugFunc = namespace + ? debug(`fetch-mock:${phase}:${namespace}`) + : debug(`fetch-mock:${phase}`); +}; -const newDebugSandbox = (ns) => debug(`fetch-mock:${phase}:${ns}`); +const newDebugSandbox = ns => debug(`fetch-mock:${phase}:${ns}`); -newDebug() +newDebug(); module.exports = { debug: (...args) => { - debugFunc(...args) + debugFunc(...args); }, setDebugNamespace: str => { - namespace = str + namespace = str; newDebug(); }, setDebugPhase: str => { phase = str || 'default'; newDebug(); }, - getDebug: (namespace) => newDebugSandbox(namespace) -} + getDebug: namespace => newDebugSandbox(namespace) +}; diff --git a/src/lib/fetch-handler.js b/src/lib/fetch-handler.js index ee00cd92..3e335ec1 100755 --- a/src/lib/fetch-handler.js +++ b/src/lib/fetch-handler.js @@ -1,4 +1,4 @@ -const {debug, setDebugPhase, getDebug} = require('./debug'); +const { debug, setDebugPhase, getDebug } = require('./debug'); const responseBuilder = require('./response-builder'); const requestUtils = require('./request-utils'); const FetchMock = {}; @@ -64,7 +64,7 @@ const resolve = async ( FetchMock.fetchHandler = function(url, options, request) { setDebugPhase('handle'); const debug = getDebug('fetchHandler()'); - debug('fetch called with:', url, options) + debug('fetch called with:', url, options); const normalizedRequest = requestUtils.normalizeRequest( url, options, @@ -75,11 +75,11 @@ FetchMock.fetchHandler = function(url, options, request) { const { signal } = normalizedRequest; - debug('Request normalised') - debug(' url', url) - debug(' options', options) - debug(' request', request) - debug(' signal', signal) + debug('Request normalised'); + debug(' url', url); + debug(' options', options); + debug(' request', request); + debug(' signal', signal); const route = this.executeRouter(url, options, request); @@ -114,7 +114,7 @@ FetchMock.fetchHandler = function(url, options, request) { .then(done, done) .then(() => { setDebugPhase(); - }) + }); }); }; @@ -188,9 +188,9 @@ FetchMock.generateResponse = async function(route, url, options, request) { FetchMock.router = function(url, options, request) { const route = this.routes.find((route, i) => { - debug(`Trying to match route ${i}`) - return route.matcher(url, options, request) - }); + debug(`Trying to match route ${i}`); + return route.matcher(url, options, request); + }); if (route) { this.push({ @@ -214,7 +214,13 @@ FetchMock.getNativeFetch = function() { }; FetchMock.push = function({ url, options, request, isUnmatched, identifier }) { - debug('Recording fetch call', { url, options, request, isUnmatched, identifier }) + debug('Recording fetch call', { + url, + options, + request, + isUnmatched, + identifier + }); const args = [url, options]; args.request = request; args.identifier = identifier; diff --git a/src/lib/generate-matcher.js b/src/lib/generate-matcher.js index 3bf8588f..36b259f0 100644 --- a/src/lib/generate-matcher.js +++ b/src/lib/generate-matcher.js @@ -1,4 +1,4 @@ -const {debug, getDebug, setDebugNamespace} = require('./debug'); +const { debug, setDebugNamespace } = require('./debug'); const glob = require('glob-to-regexp'); const pathToRegexp = require('path-to-regexp'); const querystring = require('querystring'); @@ -203,7 +203,7 @@ const getUrlMatcher = route => { }; module.exports = route => { - setDebugNamespace('generateMatcher()') + setDebugNamespace('generateMatcher()'); debug('Compiling matcher for route'); const matchers = [ route.query && getQueryStringMatcher(route), @@ -216,7 +216,7 @@ module.exports = route => { ].filter(matcher => !!matcher); debug('Compiled matcher for route'); - setDebugNamespace() + setDebugNamespace(); return (url, options = {}, request) => matchers.every(matcher => matcher(url, options, request)); }; diff --git a/src/lib/index.js b/src/lib/index.js index 601e8c87..8e296769 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -1,4 +1,4 @@ -const {debug} = require('./debug'); +const { debug } = require('./debug'); const setUpAndTearDown = require('./set-up-and-tear-down'); const fetchHandler = require('./fetch-handler'); const inspecting = require('./inspecting'); @@ -14,7 +14,7 @@ FetchMock.config = { }; FetchMock.createInstance = function() { - debug('Creating fetch-mock instance') + debug('Creating fetch-mock instance'); const instance = Object.create(FetchMock); instance._uncompiledRoutes = (this._uncompiledRoutes || []).slice(); instance.routes = instance._uncompiledRoutes.map(config => @@ -36,7 +36,7 @@ FetchMock.bindMethods = function() { }; FetchMock.sandbox = function() { - debug('Creating sandboxed fetch-mock instance') + debug('Creating sandboxed fetch-mock instance'); // this construct allows us to create a fetch-mock instance which is also // a callable function, while circumventing circularity when defining the // object that this function should be bound to diff --git a/src/lib/inspecting.js b/src/lib/inspecting.js index 3ab95a2a..b2d87518 100644 --- a/src/lib/inspecting.js +++ b/src/lib/inspecting.js @@ -1,4 +1,4 @@ -const {setDebugPhase, setDebugNamespace, debug} = require('./debug'); +const { setDebugPhase, setDebugNamespace, debug } = require('./debug'); const { normalizeUrl } = require('./request-utils'); const FetchMock = {}; const { sanitizeRoute } = require('./compile-route'); @@ -11,50 +11,57 @@ const filterCallsWithMatcher = (matcher, options = {}, calls) => { return calls.filter(([url, options]) => matcher(normalizeUrl(url), options)); }; -const formatDebug = (func) => { - return function (...args) { - setDebugPhase('inspect') +const formatDebug = func => { + return function(...args) { + setDebugPhase('inspect'); const result = func.call(this, ...args); - setDebugPhase() + setDebugPhase(); return result; - } -} + }; +}; FetchMock.filterCalls = function(nameOrMatcher, options) { - debug('Filtering fetch calls') + debug('Filtering fetch calls'); let calls = this._calls; let matcher = '*'; if ([true, 'matched'].includes(nameOrMatcher)) { - debug(`Filter provided is ${nameOrMatcher}. Returning matched calls only`) + debug(`Filter provided is ${nameOrMatcher}. Returning matched calls only`); calls = calls.filter(({ isUnmatched }) => !isUnmatched); - } else if ([false, 'unmatched'].includes(nameOrMatcher)) { - debug(`Filter provided is ${nameOrMatcher}. Returning unmatched calls only`) + debug( + `Filter provided is ${nameOrMatcher}. Returning unmatched calls only` + ); calls = calls.filter(({ isUnmatched }) => isUnmatched); } else if (typeof nameOrMatcher === 'undefined') { - debug(`Filter provided is undefined. Returning all calls`) + debug(`Filter provided is undefined. Returning all calls`); calls = calls; } else if (isName(nameOrMatcher)) { - debug(`Filter provided, looks like the name of a named route. Returning only calls handled by that route`) + debug( + `Filter provided, looks like the name of a named route. Returning only calls handled by that route` + ); calls = calls.filter(({ identifier }) => identifier === nameOrMatcher); } else { matcher = normalizeUrl(nameOrMatcher); if (this.routes.some(({ identifier }) => identifier === matcher)) { - debug(`Filter provided, ${nameOrMatcher}, identifies a route. Returning only calls handled by that route`) + debug( + `Filter provided, ${nameOrMatcher}, identifies a route. Returning only calls handled by that route` + ); calls = calls.filter(call => call.identifier === matcher); } } - if ((options || matcher !== '*') && calls.length) { if (typeof options === 'string') { options = { method: options }; } - debug('Compiling filter and options to route in order to filter all calls', nameOrMatcher) + debug( + 'Compiling filter and options to route in order to filter all calls', + nameOrMatcher + ); calls = filterCallsWithMatcher(matcher, options, calls); } - debug(`Retrieved ${calls.length} calls`) + debug(`Retrieved ${calls.length} calls`); return calls; }; @@ -84,45 +91,50 @@ FetchMock.called = formatDebug(function(nameOrMatcher, options) { }); FetchMock.flush = formatDebug(async function(waitForResponseMethods) { - setDebugNamespace('flush') - debug(`flushing all fetch calls. ${waitForResponseMethods ? '' : 'Not '}waiting for response bodies to complete download`); + setDebugNamespace('flush'); + debug( + `flushing all fetch calls. ${ + waitForResponseMethods ? '' : 'Not ' + }waiting for response bodies to complete download` + ); const queuedPromises = this._holdingPromises; this._holdingPromises = []; - debug(`${queuedPromises.length} fetch calls to be awaited`) + debug(`${queuedPromises.length} fetch calls to be awaited`); - await Promise.all(queuedPromises) - debug(`All fetch calls have completed`) + await Promise.all(queuedPromises); + debug(`All fetch calls have completed`); if (waitForResponseMethods && this._holdingPromises.length) { - debug(`Awaiting all fetch bodies to download`) + debug(`Awaiting all fetch bodies to download`); await this.flush(waitForResponseMethods); - debug(`All fetch bodies have completed downloading`) + debug(`All fetch bodies have completed downloading`); } - setDebugNamespace() + setDebugNamespace(); }); FetchMock.done = formatDebug(function(nameOrMatcher) { - setDebugPhase('inspect') - setDebugNamespace('done') - debug('Checking to see if expected calls have been made') + setDebugPhase('inspect'); + setDebugNamespace('done'); + debug('Checking to see if expected calls have been made'); let routesToCheck; - - if(nameOrMatcher && typeof nameOrMatcher !== 'boolean') { - debug('Checking to see if expected calls have been made for single route:', nameOrMatcher) - routesToCheck = [{ identifier: nameOrMatcher }] - } else { - debug('Checking to see if expected calls have been made for all routes') - routesToCheck = this.routes; - } - + if (nameOrMatcher && typeof nameOrMatcher !== 'boolean') { + debug( + 'Checking to see if expected calls have been made for single route:', + nameOrMatcher + ); + routesToCheck = [{ identifier: nameOrMatcher }]; + } else { + debug('Checking to see if expected calls have been made for all routes'); + routesToCheck = this.routes; + } // Can't use array.every because would exit after first failure, which would // break the logging const result = routesToCheck .map(({ identifier }) => { if (!this.called(identifier)) { - debug('No calls made for route:', identifier) + debug('No calls made for route:', identifier); console.warn(`Warning: ${identifier} not called`); // eslint-disable-line return false; } @@ -132,14 +144,20 @@ FetchMock.done = formatDebug(function(nameOrMatcher) { ).repeat; if (!expectedTimes) { - debug('Route has been called at least once, and no expectation of more set:', identifier) + debug( + 'Route has been called at least once, and no expectation of more set:', + identifier + ); return true; } const actualTimes = this.filterCalls(identifier).length; - debug(`Route called ${actualTimes} times:`, identifier) + debug(`Route called ${actualTimes} times:`, identifier); if (expectedTimes > actualTimes) { - debug(`Route called ${actualTimes} times, but expected ${expectedTimes}:`, identifier) + debug( + `Route called ${actualTimes} times, but expected ${expectedTimes}:`, + identifier + ); console.warn( `Warning: ${identifier} only called ${actualTimes} times, but ${expectedTimes} expected` ); // eslint-disable-line @@ -150,9 +168,9 @@ FetchMock.done = formatDebug(function(nameOrMatcher) { }) .every(isDone => isDone); - setDebugNamespace() - setDebugPhase() - return result + setDebugNamespace(); + setDebugPhase(); + return result; }); module.exports = FetchMock; diff --git a/src/lib/request-utils.js b/src/lib/request-utils.js index 9dd570fe..319c00f2 100644 --- a/src/lib/request-utils.js +++ b/src/lib/request-utils.js @@ -1,4 +1,3 @@ -const {debug} = require('./debug'); let URL; // https://stackoverflow.com/a/19709846/308237 const absoluteUrlRX = new RegExp('^(?:[a-z]+:)?//', 'i'); diff --git a/src/lib/response-builder.js b/src/lib/response-builder.js index ddb38b84..cdacf2dc 100644 --- a/src/lib/response-builder.js +++ b/src/lib/response-builder.js @@ -1,4 +1,4 @@ -const {debug, getDebug} = require('./debug'); +const { getDebug } = require('./debug'); const responseConfigProps = [ 'body', 'headers', @@ -10,12 +10,12 @@ const responseConfigProps = [ class ResponseBuilder { constructor(options) { this.debug = getDebug('ResponseBuilder()'); - this.debug('Response builder created with options', options) + this.debug('Response builder created with options', options); Object.assign(this, options); } exec() { - this.debug('building response') + this.debug('building response'); this.normalizeResponseConfig(); this.constructFetchOpts(); this.constructResponseBody(); @@ -43,14 +43,14 @@ class ResponseBuilder { normalizeResponseConfig() { // If the response config looks like a status, start to generate a simple response if (typeof this.responseConfig === 'number') { - this.debug('building response using status', this.responseConfig) + this.debug('building response using status', this.responseConfig); this.responseConfig = { status: this.responseConfig }; // If the response config is not an object, or is an object that doesn't use // any reserved properties, assume it is meant to be the body of the response } else if (typeof this.responseConfig === 'string' || this.sendAsObject()) { - this.debug('building text response from', this.responseConfig) + this.debug('building text response from', this.responseConfig); this.responseConfig = { body: this.responseConfig }; @@ -59,7 +59,7 @@ class ResponseBuilder { validateStatus(status) { if (!status) { - this.debug('No status provided. Defaulting to 200') + this.debug('No status provided. Defaulting to 200'); return 200; } @@ -69,7 +69,7 @@ class ResponseBuilder { status >= 200) || status < 600 ) { - this.debug('Valid status provided', status) + this.debug('Valid status provided', status); return status; } @@ -104,7 +104,7 @@ e.g. {"body": {"status: "registered"}}`); this.responseConfig.body != null && //eslint-disable-line typeof this.body === 'object' ) { - this.debug('Stringifying JSON response body') + this.debug('Stringifying JSON response body'); this.body = JSON.stringify(this.body); if (!this.options.headers.has('Content-Type')) { this.options.headers.set('Content-Type', 'application/json'); @@ -119,7 +119,7 @@ e.g. {"body": {"status: "registered"}}`); typeof this.body === 'string' && !this.options.headers.has('Content-Length') ) { - this.debug('Setting content-length header:', this.body.length.toString()) + this.debug('Setting content-length header:', this.body.length.toString()); this.options.headers.set('Content-Length', this.body.length.toString()); } } @@ -133,7 +133,7 @@ e.g. {"body": {"status: "registered"}}`); // On the server we need to manually construct the readable stream for the // Response object (on the client this done automatically) if (this.Stream) { - this.debug('Creating response stream') + this.debug('Creating response stream'); const stream = new this.Stream.Readable(); if (this.body != null) { //eslint-disable-line stream.push(this.body, 'utf-8'); @@ -150,26 +150,29 @@ e.g. {"body": {"status: "registered"}}`); // Using a proxy means we can set properties that may not be writable on // the original Response. It also means we can track the resolution of // promises returned by res.json(), res.text() etc - this.debug('Wrappipng Response in ES proxy for observability') + this.debug('Wrappipng Response in ES proxy for observability'); return new Proxy(response, { get: (originalResponse, name) => { if (this.responseConfig.redirectUrl) { if (name === 'url') { - this.debug('Retrieving redirect url', this.responseConfig.redirectUrl) + this.debug( + 'Retrieving redirect url', + this.responseConfig.redirectUrl + ); return this.responseConfig.redirectUrl; } if (name === 'redirected') { - this.debug('Retrieving redirected status', true) + this.debug('Retrieving redirected status', true); return true; } } if (typeof originalResponse[name] === 'function') { - this.debug('Wrapping body promises in ES proxies for observability') + this.debug('Wrapping body promises in ES proxies for observability'); return new Proxy(originalResponse[name], { apply: (func, thisArg, args) => { - this.debug(`Calling res.${name}`) + this.debug(`Calling res.${name}`); const result = func.apply(response, args); if (result.then) { fetchMock._holdingPromises.push(result.catch(() => null)); diff --git a/src/lib/set-up-and-tear-down.js b/src/lib/set-up-and-tear-down.js index ddfbc3ae..39e2d3ba 100644 --- a/src/lib/set-up-and-tear-down.js +++ b/src/lib/set-up-and-tear-down.js @@ -1,4 +1,4 @@ -const {debug, setDebugPhase} = require('./debug'); +const { debug, setDebugPhase } = require('./debug'); const { compileRoute } = require('./compile-route'); const FetchMock = {}; @@ -12,7 +12,7 @@ FetchMock.mock = function(...args) { }; FetchMock.addRoute = function(uncompiledRoute) { - debug('Adding route', uncompiledRoute) + debug('Adding route', uncompiledRoute); const route = this.compileRoute(uncompiledRoute); const clashes = this.routes.filter( ({ identifier, method }) => @@ -47,7 +47,6 @@ FetchMock.addRoute = function(uncompiledRoute) { this._uncompiledRoutes.push(uncompiledRoute); this.routes.push(route); - }; FetchMock._mock = function() {