From d5fd9931e591f33f294d1d9e1a31e53e2e78146b Mon Sep 17 00:00:00 2001 From: diosmosis Date: Wed, 2 Oct 2024 00:54:49 -0700 Subject: [PATCH] introduce indirection to be able to mock UrlFetchApp methods --- src-test/callFunctionInTest.ts | 5 +-- src-test/mock-fixtures/urlfetchapp-mock.ts | 38 ++++++++++++++-------- src/api.ts | 15 ++++++--- src/index.ts | 1 + src/services.ts | 14 ++++++++ 5 files changed, 50 insertions(+), 23 deletions(-) create mode 100644 src/services.ts diff --git a/src-test/callFunctionInTest.ts b/src-test/callFunctionInTest.ts index 74e33fe..38e16eb 100644 --- a/src-test/callFunctionInTest.ts +++ b/src-test/callFunctionInTest.ts @@ -12,7 +12,7 @@ export function callFunctionInTest(functionName: string, testName: string, ...pa console.log(`calling ${functionName} in test "${testName}"`); // there is no global object (like window) in apps script, so this is how we get a global function by name - const fn = eval(functionName); + const fn = (new Function(`return ${functionName};`))(); const result = fn(...params); return JSON.stringify(result); } catch (e) { @@ -27,9 +27,6 @@ export function callFunctionInTestWithMockFixture( ...params: unknown[] ) { const fixtureInstance = (ALL_FIXTURES[fixture.name])(...params); - console.log(fixture.name); - console.log(ALL_FIXTURES[fixture.name].toString()); - console.log(UrlFetchApp.fetchAll.toString()); fixtureInstance.setUp(); try { return callFunctionInTest(functionName, testName, ...params); diff --git a/src-test/mock-fixtures/urlfetchapp-mock.ts b/src-test/mock-fixtures/urlfetchapp-mock.ts index 4e93c67..c2cd40f 100644 --- a/src-test/mock-fixtures/urlfetchapp-mock.ts +++ b/src-test/mock-fixtures/urlfetchapp-mock.ts @@ -6,27 +6,37 @@ */ export default function urlFetchAppMock(errorToThrow: string, throwAsString: boolean = false) { - const previousFetchAll = UrlFetchApp.fetchAll; let isThrown = false; - return { - setUp() { - UrlFetchApp.fetchAll = function (...args) { - if (!isThrown) { - isThrown = true; + const mockUrlFetchApp = new Proxy(UrlFetchApp, { + get(target, prop) { + if (prop === 'fetchAll') { + return function (...args) { + if (!isThrown) { + isThrown = true; - if (throwAsString) { - throw errorToThrow; + if (throwAsString) { + throw errorToThrow; + } else { + throw new Error(errorToThrow); + } } else { - throw new Error(errorToThrow); + return target[prop].call(this, ...args); } - } else { - return previousFetchAll.call(this, ...args); - } - }; + }; + } + return target[prop]; + } + }); + + return { + setUp() { + const getServices = (new Function('return getServices;'))(); + getServices().UrlFetchApp = mockUrlFetchApp; }, tearDown() { - UrlFetchApp.fetchAll = previousFetchAll; + const getServices = (new Function('return getServices;'))(); + getServices().UrlFetchApp = UrlFetchApp; }, }; } diff --git a/src/api.ts b/src/api.ts index 2c38eac..f02b84f 100644 --- a/src/api.ts +++ b/src/api.ts @@ -10,6 +10,7 @@ import { getScriptElapsedTime } from './connector'; import { throwUnexpectedError, throwUserError } from './error'; import URLFetchRequest = GoogleAppsScript.URL_Fetch.URLFetchRequest; import { debugLog, log, logError } from './log'; +import { getServices } from './services'; const SCRIPT_RUNTIME_LIMIT = parseInt(env.SCRIPT_RUNTIME_LIMIT) || 0; const API_REQUEST_RETRY_LIMIT_IN_SECS = parseInt(env.API_REQUEST_RETRY_LIMIT_IN_SECS) || 0; @@ -117,9 +118,11 @@ function isUrlFetchErrorQuotaLimitReachedError(errorMessage: unknown) { function isUrlFetchErrorProbablyTemporary(errorMessage: unknown) { return typeof errorMessage === 'string' - && !errorMessage.toLowerCase().includes('address unavailable') - && !errorMessage.toLowerCase().includes('dns error') - && !errorMessage.toLowerCase().includes('property fetchall on object urlfetchapp'); + && ( + errorMessage.toLowerCase().includes('address unavailable') + || errorMessage.toLowerCase().includes('dns error') + || errorMessage.toLowerCase().includes('property fetchall on object urlfetchapp') + ); } /** @@ -212,9 +215,11 @@ export function fetchAll(requests: MatomoRequestParams[], options: ApiFetchOptio let responses = []; try { - responses = UrlFetchApp.fetchAll(urlsToFetch); + console.log('function is: ' + getServices().UrlFetchApp.fetchAll.toString()); + responses = getServices().UrlFetchApp.fetchAll(urlsToFetch); } catch (e) { const errorMessage = e.message || e; + console.log(`error message is: ${errorMessage}`); // throw user friendly error messages if possible if (isUrlFetchErrorQuotaLimitReachedError(errorMessage)) { @@ -222,7 +227,7 @@ export function fetchAll(requests: MatomoRequestParams[], options: ApiFetchOptio } // only rethrow for unknown errors, otherwise retry - if (isUrlFetchErrorProbablyTemporary(errorMessage)) { + if (!isUrlFetchErrorProbablyTemporary(errorMessage)) { throw e; } } diff --git a/src/index.ts b/src/index.ts index 3e17d6c..849e65d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,3 +9,4 @@ export { extractBasicAuthFromUrl, fetchAll, isApiErrorNonRandom } from './api'; export * from './auth'; export * from './config'; export * from './data'; +export * from './services'; diff --git a/src/services.ts b/src/services.ts new file mode 100644 index 0000000..16e9094 --- /dev/null +++ b/src/services.ts @@ -0,0 +1,14 @@ +/** + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +const SERVICES = { + UrlFetchApp, +}; + +export function getServices() { + return SERVICES; +}