From 7838aefb825d4aaf30b58a7af60a8c054bee8a1d Mon Sep 17 00:00:00 2001 From: Justin Canas Date: Tue, 30 Jan 2024 09:07:11 -0800 Subject: [PATCH] Revert "[IA-4620] Invalidate Leo token on sign out" (#4621) --- src/analysis/CookieProvider.test.ts | 71 --------------- src/analysis/CookieProvider.ts | 26 ------ src/auth/auth.test.ts | 90 ------------------- src/auth/auth.ts | 20 ++--- src/components/CookieWarning.js | 24 +++-- src/components/ImageDepViewer.js | 2 +- src/libs/ajax.js | 2 +- src/libs/ajax/AzureStorage.ts | 3 +- src/libs/ajax/Dockstore.test.ts | 13 +-- src/libs/ajax/Dockstore.ts | 3 +- src/libs/ajax/GoogleStorage.ts | 2 +- src/libs/ajax/Metrics.ts | 1 - src/libs/ajax/Support.ts | 3 +- src/libs/ajax/ajax-common.ts | 52 ++++++++++- ...zureBlobStorageFileBrowserProvider.test.ts | 13 +-- .../AzureBlobStorageFileBrowserProvider.ts | 2 +- src/libs/ajax/leonardo/Runtimes.ts | 8 +- src/libs/ajax/network-core/fetch-core.ts | 49 ---------- src/libs/ajax/workflows-app/CbasPact.test.js | 9 +- src/libs/ajax/workflows-app/WorkflowScript.js | 2 +- 20 files changed, 91 insertions(+), 304 deletions(-) delete mode 100644 src/analysis/CookieProvider.test.ts delete mode 100644 src/analysis/CookieProvider.ts delete mode 100644 src/auth/auth.test.ts delete mode 100644 src/libs/ajax/network-core/fetch-core.ts diff --git a/src/analysis/CookieProvider.test.ts b/src/analysis/CookieProvider.test.ts deleted file mode 100644 index a81dd80b16..0000000000 --- a/src/analysis/CookieProvider.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { leoCookieProvider } from 'src/analysis/CookieProvider'; -import { Ajax } from 'src/libs/ajax'; -import { RuntimesAjaxContract } from 'src/libs/ajax/leonardo/Runtimes'; -import { asMockedFn } from 'src/testing/test-utils'; - -jest.mock('src/libs/ajax'); - -/** - * local test utility - mocks the Ajax super-object and the subset of needed multi-contracts it - * returns with as much type-safety as possible. - * - * @return collection of key contract sub-objects for easy - * mock overrides and/or method spying/assertions - */ -type AjaxContract = ReturnType; -type RuntimesNeeds = Pick; -interface AjaxMockNeeds { - Runtimes: RuntimesNeeds; -} - -const mockAjaxNeeds = (): AjaxMockNeeds => { - const partialRuntimes: RuntimesNeeds = { - invalidateCookie: jest.fn(), - }; - const mockRuntimes = partialRuntimes as RuntimesAjaxContract; - - asMockedFn(Ajax).mockReturnValue({ Runtimes: mockRuntimes } as AjaxContract); - - return { - Runtimes: partialRuntimes, - }; -}; -describe('CookieProvider', () => { - it('calls the leo endpoint on invalidateCookie', async () => { - const ajaxMock = mockAjaxNeeds(); - - // Act - await leoCookieProvider.invalidateCookies(); - - // Assert - expect(Ajax).toBeCalledTimes(1); - expect(ajaxMock.Runtimes.invalidateCookie).toBeCalledTimes(1); - }); - - it('does not error if api returns 401', async () => { - const ajaxMock = mockAjaxNeeds(); - asMockedFn(ajaxMock.Runtimes.invalidateCookie).mockImplementation(() => - Promise.reject(new Response(JSON.stringify({ success: false }), { status: 401 })) - ); - const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); - - // Act - await leoCookieProvider.invalidateCookies(); - - expect(Ajax).toBeCalledTimes(1); - expect(ajaxMock.Runtimes.invalidateCookie).toBeCalledTimes(1); - expect(errorSpy).toBeCalledTimes(1); - }); - - it('throws non 401 errors', async () => { - // Arrange - const ajaxMock = mockAjaxNeeds(); - asMockedFn(ajaxMock.Runtimes.invalidateCookie).mockImplementation(() => Promise.reject(new Error('test error'))); - - // Act - const errorPromise = leoCookieProvider.invalidateCookies(); - - // Assert - await expect(errorPromise).rejects.toEqual(new Error('test error')); - }); -}); diff --git a/src/analysis/CookieProvider.ts b/src/analysis/CookieProvider.ts deleted file mode 100644 index 05a2bf549c..0000000000 --- a/src/analysis/CookieProvider.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Ajax } from 'src/libs/ajax'; -import { azureCookieReadyStore, cookieReadyStore } from 'src/libs/state'; - -export interface LeoCookieProvider { - invalidateCookies: () => Promise; -} - -export const leoCookieProvider: LeoCookieProvider = { - invalidateCookies: async () => { - // TODO: call azure invalidate cookie once endpoint exists, https://broadworkbench.atlassian.net/browse/IA-3498 - // TODO: use runtime provider here - - try { - await Ajax().Runtimes.invalidateCookie(); - } catch (error) { - if (error instanceof Response && error.status === 401) { - console.error('in error block for invalid cookie. This is expected if the token is expired', error); - } else { - throw error; - } - } - - cookieReadyStore.reset(); - azureCookieReadyStore.reset(); - }, -}; diff --git a/src/auth/auth.test.ts b/src/auth/auth.test.ts deleted file mode 100644 index c1955159b4..0000000000 --- a/src/auth/auth.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { asMockedFn } from '@terra-ui-packages/test-utils'; -import { signOut } from 'src/auth/auth'; -import { revokeTokens } from 'src/auth/oidc-broker'; -import { Ajax } from 'src/libs/ajax'; -import { withRetryAfterReloadingExpiredAuthToken } from 'src/libs/ajax/ajax-common'; -import { MetricsContract } from 'src/libs/ajax/Metrics'; -import { fetchOk } from 'src/libs/ajax/network-core/fetch-core'; - -type AjaxCommonExports = typeof import('src/libs/ajax/ajax-common'); -jest.mock( - 'src/libs/ajax/ajax-common', - (): AjaxCommonExports => ({ - ...jest.requireActual('src/libs/ajax/ajax-common'), - withRetryAfterReloadingExpiredAuthToken: jest.fn(), - }) -); - -type FetchCoreExports = typeof import('src/libs/ajax/network-core/fetch-core'); -jest.mock( - 'src/libs/ajax/network-core/fetch-core', - (): FetchCoreExports => ({ - ...jest.requireActual('src/libs/ajax/network-core/fetch-core'), - fetchOk: jest.fn(), - }) -); - -type AjaxExports = typeof import('src/libs/ajax'); -jest.mock('src/libs/ajax'); -type AjaxContract = ReturnType; - -type MetricsNeeds = Pick; -interface AjaxMockNeeds { - Metrics: MetricsNeeds; -} - -type OidcExports = typeof import('src/auth/oidc-broker'); -jest.mock( - 'src/auth/oidc-broker', - (): OidcExports => ({ - ...jest.requireActual('src/auth/oidc-broker'), - revokeTokens: jest.fn(), - }) -); - -const mockAjaxNeeds = (): AjaxMockNeeds => { - const partialMetrics: MetricsNeeds = { - captureEvent: jest.fn(), - }; - - const actualAjax: AjaxExports = jest.requireActual('src/libs/ajax'); - const actualRuntimes = actualAjax.Ajax().Runtimes; - const mockMetrics = partialMetrics as MetricsContract; - - asMockedFn(Ajax).mockReturnValue({ - Runtimes: actualRuntimes, - Metrics: mockMetrics, - } as AjaxContract); - - return { - Metrics: partialMetrics, - }; -}; - -describe('auth', () => { - describe('signOut', () => { - // This test mocks all api calls in sign out to throw an error, and ensures sign out functions properly - // This is a very important test to update whenever you add API calls to sign out - // There have been multiple production incidents when the code does not properly ignore errors in API calls in sign out - it('properly signs out when underlying an API call errors due to token expiration', async () => { - // Arrange - const mockAjax = mockAjaxNeeds(); - asMockedFn(mockAjax.Metrics.captureEvent).mockImplementation(() => Promise.reject('capture event error')); - asMockedFn(fetchOk).mockImplementation(() => - Promise.reject(new Response(JSON.stringify({ success: false }), { status: 401 })) - ); - asMockedFn(revokeTokens).mockImplementation(() => Promise.reject('revoke token error')); - jest.spyOn(console, 'error').mockImplementation(() => {}); - - // Act - await signOut(); - - // Assert - expect(Ajax).toBeCalledTimes(2); - expect(mockAjax.Metrics.captureEvent).toBeCalledTimes(1); - expect(revokeTokens).toBeCalledTimes(1); - expect(withRetryAfterReloadingExpiredAuthToken).toBeCalledTimes(0); - expect(fetchOk).toBeCalledTimes(1); - }); - }); -}); diff --git a/src/auth/auth.ts b/src/auth/auth.ts index 03dffc5ec7..74630f4f0f 100644 --- a/src/auth/auth.ts +++ b/src/auth/auth.ts @@ -2,7 +2,6 @@ import { DEFAULT, switchCase } from '@terra-ui-packages/core-utils'; import { parseJSON } from 'date-fns/fp'; import jwtDecode, { JwtPayload } from 'jwt-decode'; import _ from 'lodash/fp'; -import { leoCookieProvider } from 'src/analysis/CookieProvider'; import { sessionTimedOutErrorMessage } from 'src/auth/auth-errors'; import { B2cIdTokenClaims, @@ -15,7 +14,7 @@ import { } from 'src/auth/oidc-broker'; import { cookiesAcceptedKey } from 'src/components/CookieWarning'; import { Ajax } from 'src/libs/ajax'; -import { fetchOk } from 'src/libs/ajax/network-core/fetch-core'; +import { fetchOk } from 'src/libs/ajax/ajax-common'; import { SamUserAttributes } from 'src/libs/ajax/User'; import { getSessionStorage } from 'src/libs/browser-storage'; import { withErrorIgnoring, withErrorReporting } from 'src/libs/error'; @@ -66,7 +65,7 @@ export type SignOutCause = | 'idleStatusMonitor' | 'unspecified'; -export const sendSignOutMetrics = async (cause: SignOutCause): Promise => { +const sendSignOutMetrics = async (cause: SignOutCause): Promise => { const eventToFire: MetricsEventName = switchCase( cause, ['requested', () => Events.user.signOut.requested], @@ -101,26 +100,17 @@ export const sendAuthTokenDesyncMetric = () => { Ajax().Metrics.captureEvent(Events.user.authToken.desync, {}); }; -// This can be called with an expired token, be careful of making any API calls here -// Any API calls that rely on tokens should fail silently -export const signOut = async (cause: SignOutCause = 'unspecified') => { - // The underlying API calls are already wrapped with `withErrorIgnoring`, but we include it here too to be defensive - // It is easier to test this way, and if the underlying API call were to remove that it, - // it would cause a serious bug that is hard for a developer to find/test when the token expires after 24 hours. - await sendSignOutMetrics(cause).catch(_.noop); - +export const signOut = (cause: SignOutCause = 'unspecified'): void => { + sendSignOutMetrics(cause); if (cause === 'expiredRefreshToken' || cause === 'errorRefreshingAuthToken') { notify('info', sessionTimedOutErrorMessage, sessionTimeoutProps); } - // TODO: invalidate runtime cookies https://broadworkbench.atlassian.net/browse/IA-3498 cookieReadyStore.reset(); azureCookieReadyStore.reset(); getSessionStorage().clear(); - await leoCookieProvider.invalidateCookies(); - getSessionStorage().clear(); - await revokeTokens().catch(_.noop); + revokeTokens(); const { cookiesAccepted } = authStore.get(); diff --git a/src/components/CookieWarning.js b/src/components/CookieWarning.js index 30008e7e4d..7f17b92862 100644 --- a/src/components/CookieWarning.js +++ b/src/components/CookieWarning.js @@ -2,14 +2,14 @@ import _ from 'lodash/fp'; import { useEffect, useRef, useState } from 'react'; import { aside, div, h } from 'react-hyperscript-helpers'; import { Transition } from 'react-transition-group'; -import { leoCookieProvider } from 'src/analysis/CookieProvider'; import { ButtonPrimary, ButtonSecondary, Link } from 'src/components/common'; +import { Ajax } from 'src/libs/ajax'; import { getEnabledBrand } from 'src/libs/brand-utils'; import { getSessionStorage } from 'src/libs/browser-storage'; import colors from 'src/libs/colors'; import * as Nav from 'src/libs/nav'; import { useCancellation, useStore } from 'src/libs/react-utils'; -import { authStore } from 'src/libs/state'; +import { authStore, azureCookieReadyStore, cookieReadyStore } from 'src/libs/state'; export const cookiesAcceptedKey = 'cookiesAccepted'; @@ -22,10 +22,10 @@ const transitionStyle = { const CookieWarning = () => { const animTime = 0.3; + const signal = useCancellation(); const [showWarning, setShowWarning] = useState(false); const { cookiesAccepted } = useStore(authStore); const timeout = useRef(); - const signal = useCancellation(); const brand = getEnabledBrand(); const acceptCookies = (acceptedCookies) => { @@ -45,9 +45,23 @@ const CookieWarning = () => { }, [cookiesAccepted]); const rejectCookies = async () => { - await leoCookieProvider.invalidateCookies({ signal }); - getSessionStorage().clear(); + const cookies = document.cookie.split(';'); acceptCookies(false); + // TODO: call azure invalidate cookie once endpoint exists, https://broadworkbench.atlassian.net/browse/IA-3498 + await Ajax(signal) + .Runtimes.invalidateCookie() + .catch(() => {}); + // Expire all cookies + _.forEach((cookie) => { + // Find an equals sign and uses it to grab the substring of the cookie that is its name + const eqPos = cookie.indexOf('='); + const cookieName = eqPos > -1 ? cookie.substr(0, eqPos) : cookie; + document.cookie = `${cookieName}=;expires=Thu, 01 Jan 1970 00:00:00 GMT`; + }, cookies); + + cookieReadyStore.reset(); + azureCookieReadyStore.reset(); + getSessionStorage().clear(); }; return h( diff --git a/src/components/ImageDepViewer.js b/src/components/ImageDepViewer.js index 980d0cf6b5..ace98e63f5 100644 --- a/src/components/ImageDepViewer.js +++ b/src/components/ImageDepViewer.js @@ -2,7 +2,7 @@ import _ from 'lodash/fp'; import { Fragment, useEffect, useState } from 'react'; import { div, h, table, tbody, td, thead, tr } from 'react-hyperscript-helpers'; import { Select } from 'src/components/common'; -import { fetchOk } from 'src/libs/ajax/network-core/fetch-core'; +import { fetchOk } from 'src/libs/ajax/ajax-common'; import { withErrorReporting } from 'src/libs/error'; import { useCancellation } from 'src/libs/react-utils'; diff --git a/src/libs/ajax.js b/src/libs/ajax.js index e960dc8750..d36134656b 100644 --- a/src/libs/ajax.js +++ b/src/libs/ajax.js @@ -7,6 +7,7 @@ import { fetchDrsHub, fetchGoogleForms, fetchMartha, + fetchOk, fetchOrchestration, fetchRawls, jsonBody, @@ -22,7 +23,6 @@ import { Apps } from 'src/libs/ajax/leonardo/Apps'; import { Disks } from 'src/libs/ajax/leonardo/Disks'; import { Runtimes } from 'src/libs/ajax/leonardo/Runtimes'; import { Metrics } from 'src/libs/ajax/Metrics'; -import { fetchOk } from 'src/libs/ajax/network-core/fetch-core'; import { OAuth2 } from 'src/libs/ajax/OAuth2'; import { SamResources } from 'src/libs/ajax/SamResources'; import { Support } from 'src/libs/ajax/Support'; diff --git a/src/libs/ajax/AzureStorage.ts b/src/libs/ajax/AzureStorage.ts index 717a6535d9..9b98379461 100644 --- a/src/libs/ajax/AzureStorage.ts +++ b/src/libs/ajax/AzureStorage.ts @@ -3,8 +3,7 @@ import { AnalysisFile, AnalysisFileMetadata } from 'src/analysis/useAnalysisFile import { AbsolutePath, getDisplayName, getExtension, getFileName } from 'src/analysis/utils/file-utils'; import { runtimeToolLabels } from 'src/analysis/utils/tool-utils'; import { Ajax } from 'src/libs/ajax'; -import { authOpts, fetchWorkspaceManager } from 'src/libs/ajax/ajax-common'; -import { fetchOk } from 'src/libs/ajax/network-core/fetch-core'; +import { authOpts, fetchOk, fetchWorkspaceManager } from 'src/libs/ajax/ajax-common'; import { getConfig } from 'src/libs/config'; import * as Utils from 'src/libs/utils'; import { cloudProviderTypes } from 'src/libs/workspace-utils'; diff --git a/src/libs/ajax/Dockstore.test.ts b/src/libs/ajax/Dockstore.test.ts index 3788322f53..37b605fdf4 100644 --- a/src/libs/ajax/Dockstore.test.ts +++ b/src/libs/ajax/Dockstore.test.ts @@ -1,25 +1,16 @@ -import { fetchOk } from 'src/libs/ajax/network-core/fetch-core'; import { asMockedFn } from 'src/testing/test-utils'; -import { fetchDockstore } from './ajax-common'; +import { fetchDockstore, fetchOk } from './ajax-common'; import { Dockstore } from './Dockstore'; type AjaxCommonExports = typeof import('./ajax-common'); jest.mock('./ajax-common', (): Partial => { return { fetchDockstore: jest.fn(), + fetchOk: jest.fn(), }; }); -type FetchCoreExports = typeof import('src/libs/ajax/network-core/fetch-core'); -jest.mock( - 'src/libs/ajax/network-core/fetch-core', - (): FetchCoreExports => ({ - ...jest.requireActual('src/libs/ajax/network-core/fetch-core'), - fetchOk: jest.fn(), - }) -); - describe('Dockstore', () => { afterEach(() => { jest.resetAllMocks(); diff --git a/src/libs/ajax/Dockstore.ts b/src/libs/ajax/Dockstore.ts index ffb2e6d95c..c846826784 100644 --- a/src/libs/ajax/Dockstore.ts +++ b/src/libs/ajax/Dockstore.ts @@ -1,7 +1,6 @@ import * as qs from 'qs'; -import { fetchOk } from 'src/libs/ajax/network-core/fetch-core'; -import { fetchDockstore } from './ajax-common'; +import { fetchDockstore, fetchOk } from './ajax-common'; export type DockstoreWorkflowDescriptor = { path: string; diff --git a/src/libs/ajax/GoogleStorage.ts b/src/libs/ajax/GoogleStorage.ts index 5d624c3e97..2e6dba441f 100644 --- a/src/libs/ajax/GoogleStorage.ts +++ b/src/libs/ajax/GoogleStorage.ts @@ -12,13 +12,13 @@ import { getAuthToken } from 'src/auth/auth'; import { authOpts, checkRequesterPaysError, + fetchOk, fetchSam, jsonBody, withRetryOnError, withUrlPrefix, } from 'src/libs/ajax/ajax-common'; import { canUseWorkspaceProject } from 'src/libs/ajax/Billing'; -import { fetchOk } from 'src/libs/ajax/network-core/fetch-core'; import { getConfig } from 'src/libs/config'; import { knownBucketRequesterPaysStatuses, requesterPaysProjectStore, workspaceStore } from 'src/libs/state'; import * as Utils from 'src/libs/utils'; diff --git a/src/libs/ajax/Metrics.ts b/src/libs/ajax/Metrics.ts index bc9c716b83..784d9972be 100644 --- a/src/libs/ajax/Metrics.ts +++ b/src/libs/ajax/Metrics.ts @@ -87,4 +87,3 @@ export const Metrics = (signal?: AbortSignal) => { }) as (anonId: string) => Promise, }; }; -export type MetricsContract = ReturnType; diff --git a/src/libs/ajax/Support.ts b/src/libs/ajax/Support.ts index 04e78a312e..a56a483292 100644 --- a/src/libs/ajax/Support.ts +++ b/src/libs/ajax/Support.ts @@ -1,6 +1,5 @@ import _ from 'lodash/fp'; -import { jsonBody } from 'src/libs/ajax/ajax-common'; -import { fetchOk } from 'src/libs/ajax/network-core/fetch-core'; +import { fetchOk, jsonBody } from 'src/libs/ajax/ajax-common'; export interface ZendeskCreateSupportRequestRequest { name: string; diff --git a/src/libs/ajax/ajax-common.ts b/src/libs/ajax/ajax-common.ts index 21d1075f64..a2be024d21 100644 --- a/src/libs/ajax/ajax-common.ts +++ b/src/libs/ajax/ajax-common.ts @@ -1,4 +1,4 @@ -import { delay } from '@terra-ui-packages/core-utils'; +import { abandonedPromise, delay } from '@terra-ui-packages/core-utils'; import _ from 'lodash/fp'; import { AuthTokenState, @@ -11,8 +11,8 @@ import { SignOutCause, } from 'src/auth/auth'; import { sessionTimedOutErrorMessage } from 'src/auth/auth-errors'; -import { FetchFn, fetchOk, withCancellation, withInstrumentation } from 'src/libs/ajax/network-core/fetch-core'; import { getConfig } from 'src/libs/config'; +import { ajaxOverridesStore } from 'src/libs/state'; export const authOpts = (token = getAuthToken()) => ({ headers: { Authorization: `Bearer ${token}` } }); export const jsonBody = (body) => ({ @@ -21,6 +21,8 @@ export const jsonBody = (body) => ({ }); export const appIdentifier = { headers: { 'X-App-ID': 'Saturn' } }; +type FetchFn = typeof fetch; + export const withUrlPrefix = _.curry((prefix, wrappedFetch) => (path, ...args) => { return wrappedFetch(prefix + path, ...args); }); @@ -160,13 +162,55 @@ export const responseContainsRequesterPaysError = (responseText) => { return _.includes('requester pays', responseText); }; +// Allows use of ajaxOverrideStore to stub responses for testing +const withInstrumentation = + (wrappedFetch) => + (...args) => { + return _.flow( + ..._.map( + 'fn', + _.filter(({ filter }) => { + const [url, { method = 'GET' } = {}] = args; + return _.isFunction(filter) + ? filter(...args) + : url.match(filter.url) && (!filter.method || filter.method === method); + }, ajaxOverridesStore.get()) + ) + )(wrappedFetch)(...args); + }; + +// Ignores cancellation error when request is cancelled +const withCancellation = + (wrappedFetch) => + async (...args) => { + try { + return await wrappedFetch(...args); + } catch (error) { + if (error instanceof DOMException && error.name === 'AbortError') { + return abandonedPromise(); + } + throw error; + } + }; + +// Converts non-200 responses to exceptions +const withErrorRejection = + (wrappedFetch) => + async (...args) => { + const res = await wrappedFetch(...args); + if (res.ok) { + return res; + } + throw res; + }; + +export const fetchOk = _.flow(withInstrumentation, withCancellation, withErrorRejection)(fetch); + export const fetchLeo = _.flow( withUrlPrefix(`${getConfig().leoUrlRoot}/`), withRetryAfterReloadingExpiredAuthToken )(fetchOk); -export const fetchLeoWithoutAuthRetry = _.flow(withUrlPrefix(`${getConfig().leoUrlRoot}/`))(fetchOk); - export const fetchSam = _.flow( withUrlPrefix(`${getConfig().samUrlRoot}/`), withAppIdentifier, diff --git a/src/libs/ajax/file-browser-providers/AzureBlobStorageFileBrowserProvider.test.ts b/src/libs/ajax/file-browser-providers/AzureBlobStorageFileBrowserProvider.test.ts index 89759e463c..6d5f57e170 100644 --- a/src/libs/ajax/file-browser-providers/AzureBlobStorageFileBrowserProvider.test.ts +++ b/src/libs/ajax/file-browser-providers/AzureBlobStorageFileBrowserProvider.test.ts @@ -1,20 +1,15 @@ import * as qs from 'qs'; +import { fetchOk } from 'src/libs/ajax/ajax-common'; import { AzureStorageContract } from 'src/libs/ajax/AzureStorage'; import AzureBlobStorageFileBrowserProvider from 'src/libs/ajax/file-browser-providers/AzureBlobStorageFileBrowserProvider'; import { FileBrowserDirectory, FileBrowserFile } from 'src/libs/ajax/file-browser-providers/FileBrowserProvider'; -import { fetchOk } from 'src/libs/ajax/network-core/fetch-core'; import { asMockedFn } from 'src/testing/test-utils'; import * as Utils from '../../utils'; -type FetchCoreExports = typeof import('src/libs/ajax/network-core/fetch-core'); -jest.mock( - 'src/libs/ajax/network-core/fetch-core', - (): FetchCoreExports => ({ - ...jest.requireActual('src/libs/ajax/network-core/fetch-core'), - fetchOk: jest.fn(), - }) -); +jest.mock('src/libs/ajax/ajax-common', () => ({ + fetchOk: jest.fn(), +})); jest.mock('src/libs/ajax/AzureStorage', () => ({ AzureStorage: () => diff --git a/src/libs/ajax/file-browser-providers/AzureBlobStorageFileBrowserProvider.ts b/src/libs/ajax/file-browser-providers/AzureBlobStorageFileBrowserProvider.ts index 506427103e..3b0b306b28 100644 --- a/src/libs/ajax/file-browser-providers/AzureBlobStorageFileBrowserProvider.ts +++ b/src/libs/ajax/file-browser-providers/AzureBlobStorageFileBrowserProvider.ts @@ -1,7 +1,7 @@ +import { fetchOk } from 'src/libs/ajax/ajax-common'; import { AzureStorage } from 'src/libs/ajax/AzureStorage'; import FileBrowserProvider from 'src/libs/ajax/file-browser-providers/FileBrowserProvider'; import IncrementalResponse from 'src/libs/ajax/incremental-response/IncrementalResponse'; -import { fetchOk } from 'src/libs/ajax/network-core/fetch-core'; import * as Utils from 'src/libs/utils'; export interface AzureBlobStorageFileBrowserProviderParams { diff --git a/src/libs/ajax/leonardo/Runtimes.ts b/src/libs/ajax/leonardo/Runtimes.ts index 76af2bda87..417450b7b3 100644 --- a/src/libs/ajax/leonardo/Runtimes.ts +++ b/src/libs/ajax/leonardo/Runtimes.ts @@ -7,7 +7,7 @@ import { DEFAULT_RETRY_COUNT, DEFAULT_TIMEOUT_DURATION, fetchLeo, - fetchLeoWithoutAuthRetry, + fetchOk, jsonBody, makeRequestRetry, } from 'src/libs/ajax/ajax-common'; @@ -27,7 +27,6 @@ import { RawGetRuntimeItem, RawListRuntimeItem, } from 'src/libs/ajax/leonardo/models/runtime-models'; -import { fetchOk } from 'src/libs/ajax/network-core/fetch-core'; import { getConfig } from 'src/libs/config'; import { CloudPlatform } from 'src/pages/billing/models/BillingProject'; @@ -178,9 +177,8 @@ export const Runtimes = (signal: AbortSignal) => { return normalizedRuntimes; }, - invalidateCookie: (): Promise => { - // All other fetch wrappers retry auth. We should not retry if this call fails due to auth related reason here, the cookie is already invalid. - return fetchLeoWithoutAuthRetry('proxy/invalidateToken', _.merge(authOpts(), { signal, credentials: 'include' })); + invalidateCookie: () => { + return fetchLeo('proxy/invalidateToken', _.merge(authOpts(), { signal })); }, setCookie: () => { diff --git a/src/libs/ajax/network-core/fetch-core.ts b/src/libs/ajax/network-core/fetch-core.ts deleted file mode 100644 index 2585adfdb0..0000000000 --- a/src/libs/ajax/network-core/fetch-core.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { abandonedPromise } from '@terra-ui-packages/core-utils'; -import _ from 'lodash/fp'; -import { ajaxOverridesStore } from 'src/libs/state'; - -export type FetchFn = typeof fetch; - -// Allows use of ajaxOverrideStore to stub responses for testing -export const withInstrumentation = - (wrappedFetch: FetchFn) => - (...args) => { - return _.flow( - ..._.map( - 'fn', - _.filter(({ filter }) => { - const [url, { method = 'GET' } = {}] = args; - return _.isFunction(filter) - ? filter(...args) - : url.match(filter.url) && (!filter.method || filter.method === method); - }, ajaxOverridesStore.get() as any) - ) - )(wrappedFetch)(...args); - }; - -// Ignores cancellation error when request is cancelled -export const withCancellation = - (wrappedFetch) => - async (...args) => { - try { - return await wrappedFetch(...args); - } catch (error) { - if (error instanceof DOMException && error.name === 'AbortError') { - return abandonedPromise(); - } - throw error; - } - }; - -// Converts non-200 responses to exceptions -const withErrorRejection = - (wrappedFetch) => - async (...args) => { - const res = await wrappedFetch(...args); - if (res.ok) { - return res; - } - throw res; - }; - -export const fetchOk = _.flow(withInstrumentation, withCancellation, withErrorRejection)(fetch); diff --git a/src/libs/ajax/workflows-app/CbasPact.test.js b/src/libs/ajax/workflows-app/CbasPact.test.js index dd1c048789..bb88b9aadc 100644 --- a/src/libs/ajax/workflows-app/CbasPact.test.js +++ b/src/libs/ajax/workflows-app/CbasPact.test.js @@ -2,8 +2,7 @@ import 'setimmediate'; import { MatchersV3, PactV3, SpecificationVersion } from '@pact-foundation/pact'; import path from 'path'; -import { fetchFromProxy, jsonBody } from 'src/libs/ajax/ajax-common'; -import { fetchOk } from 'src/libs/ajax/network-core/fetch-core'; +import { fetchFromProxy, fetchOk, jsonBody } from 'src/libs/ajax/ajax-common'; import { Cbas } from 'src/libs/ajax/workflows-app/Cbas'; import { runSetInputDef, @@ -15,12 +14,8 @@ import { jest.mock('src/libs/ajax/ajax-common', () => ({ ...jest.requireActual('src/libs/ajax/ajax-common'), fetchFromProxy: jest.fn(), - authOpts: jest.fn(), -})); - -jest.mock('src/libs/ajax/network-core/fetch-core', () => ({ - ...jest.requireActual('src/libs/ajax/network-core/fetch-core'), fetchOk: jest.fn(), + authOpts: jest.fn(), })); jest.mock('src/auth/auth', () => { diff --git a/src/libs/ajax/workflows-app/WorkflowScript.js b/src/libs/ajax/workflows-app/WorkflowScript.js index b842d0bfe4..c3ac229626 100644 --- a/src/libs/ajax/workflows-app/WorkflowScript.js +++ b/src/libs/ajax/workflows-app/WorkflowScript.js @@ -1,4 +1,4 @@ -import { fetchOk } from 'src/libs/ajax/network-core/fetch-core'; +import { fetchOk } from 'src/libs/ajax/ajax-common'; export const WorkflowScript = (signal) => ({ get: async (workflowUrl) => {