Skip to content

Commit

Permalink
UIE-204 Narrow Ajax Usage pt12 (#5173)
Browse files Browse the repository at this point in the history
  • Loading branch information
msilva-broad authored Nov 19, 2024
1 parent 2a8f737 commit 251a323
Show file tree
Hide file tree
Showing 15 changed files with 206 additions and 247 deletions.
2 changes: 2 additions & 0 deletions src/libs/ajax/ExternalCredentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,5 @@ export const ExternalCredentials = (signal?: AbortSignal) => (oAuth2Provider: OA
},
};
};

export type ExternalCredentialsContract = ReturnType<ReturnType<typeof ExternalCredentials>>;
1 change: 1 addition & 0 deletions src/libs/ajax/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,3 +346,4 @@ export const User = (signal?: AbortSignal) => {
};

export type UserContract = ReturnType<typeof User>;
export type UserProfileContract = UserContract['profile'];
4 changes: 2 additions & 2 deletions src/profile/external-identities/LinkOAuth2Account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ClickableProps } from '@terra-ui-packages/components';
import React, { ReactNode } from 'react';
import { ButtonPrimary, Link } from 'src/components/common';
import { icon } from 'src/components/icons';
import { Ajax } from 'src/libs/ajax';
import { ExternalCredentials } from 'src/libs/ajax/ExternalCredentials';
import { withErrorReporting } from 'src/libs/error';
import { useCancellation } from 'src/libs/react-utils';
import * as Utils from 'src/libs/utils';
Expand All @@ -22,7 +22,7 @@ export const LinkOAuth2Account = (props: LinkOAuth2AccountProps): ReactNode => {

const getAuthUrlAndRedirect = withErrorReporting(`Error getting Authorization URL for ${provider.short}`)(
async () => {
const url = await Ajax(signal).ExternalCredentials(provider).getAuthorizationUrl();
const url = await ExternalCredentials(signal)(provider).getAuthorizationUrl();
window.open(url, Utils.newTabLinkProps.target, 'noopener,noreferrer');
}
);
Expand Down
33 changes: 14 additions & 19 deletions src/profile/external-identities/NihAccount.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { act, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { h } from 'react-hyperscript-helpers';
import { Ajax } from 'src/libs/ajax';
import { Metrics, MetricsContract } from 'src/libs/ajax/Metrics';
import { User, UserContract } from 'src/libs/ajax/User';
import { authStore } from 'src/libs/state';
import { NihAccount } from 'src/profile/external-identities/NihAccount';
import { asMockedFn, renderWithAppContexts } from 'src/testing/test-utils';
import { asMockedFn, MockedFn, partial, renderWithAppContexts } from 'src/testing/test-utils';

jest.mock('src/libs/nav', () => ({
...jest.requireActual('src/libs/nav'),
Expand All @@ -25,7 +26,8 @@ jest.mock('react-notifications-component', () => {
};
});

jest.mock('src/libs/ajax');
jest.mock('src/libs/ajax/Metrics');
jest.mock('src/libs/ajax/User');

const nihStatus = {
linkedNihUsername: 'TEST_USERNAME',
Expand All @@ -42,14 +44,10 @@ describe('NihAccount', () => {
describe('when given a token', () => {
it('links the NIH Account and renders a linked NIH Account', async () => {
// Arrange
const linkNihAccountFunction = jest.fn().mockReturnValue(Promise.resolve(nihStatus));
asMockedFn(Ajax).mockImplementation(
() =>
({
Metrics: { captureEvent: jest.fn() } as Partial<ReturnType<typeof Ajax>['Metrics']>,
User: { linkNihAccount: linkNihAccountFunction } as Partial<ReturnType<typeof Ajax>['User']>,
} as ReturnType<typeof Ajax>)
);
const linkNihAccountFunction: MockedFn<UserContract['linkNihAccount']> = jest.fn();
linkNihAccountFunction.mockResolvedValue(nihStatus);
asMockedFn(Metrics).mockReturnValue(partial<MetricsContract>({ captureEvent: jest.fn() }));
asMockedFn(User).mockReturnValue(partial<UserContract>({ linkNihAccount: linkNihAccountFunction }));

// Act
await act(async () => {
Expand Down Expand Up @@ -115,14 +113,11 @@ describe('NihAccount', () => {
// Arrange
const user = userEvent.setup();

const unlinkNihAccountFunction = jest.fn().mockReturnValue(Promise.resolve());
asMockedFn(Ajax).mockImplementation(
() =>
({
Metrics: { captureEvent: jest.fn() } as Partial<ReturnType<typeof Ajax>['Metrics']>,
User: { unlinkNihAccount: unlinkNihAccountFunction } as Partial<ReturnType<typeof Ajax>['User']>,
} as ReturnType<typeof Ajax>)
);
const unlinkNihAccountFunction: MockedFn<UserContract['unlinkNihAccount']> = jest.fn();
unlinkNihAccountFunction.mockResolvedValue(undefined);
asMockedFn(Metrics).mockReturnValue(partial<MetricsContract>({ captureEvent: jest.fn() }));
asMockedFn(User).mockReturnValue(partial<UserContract>({ unlinkNihAccount: unlinkNihAccountFunction }));

// Act
await act(async () => {
authStore.update((state) => ({ ...state, nihStatus, nihStatusLoaded: true }));
Expand Down
6 changes: 3 additions & 3 deletions src/profile/external-identities/NihAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { div, h, h3, span } from 'react-hyperscript-helpers';
import Collapse from 'src/components/Collapse';
import { ButtonPrimary, Link, spinnerOverlay } from 'src/components/common';
import { InfoBox } from 'src/components/InfoBox';
import { Ajax } from 'src/libs/ajax';
import { User } from 'src/libs/ajax/User';
import { NihDatasetPermission } from 'src/libs/ajax/User';
import { withErrorReporting } from 'src/libs/error';
import * as Nav from 'src/libs/nav';
Expand All @@ -30,7 +30,7 @@ export const NihAccount = ({ nihToken }) => {
withErrorReporting('Error linking NIH account'),
Utils.withBusyState(setIsLinking)
)(async () => {
const nihStatus = await Ajax().User.linkNihAccount(nihToken);
const nihStatus = await User().linkNihAccount(nihToken);
authStore.update((oldState: AuthState) => ({
...oldState,
nihStatus,
Expand Down Expand Up @@ -157,7 +157,7 @@ export const NihAccount = ({ nihToken }) => {
Utils.withBusyState(setIsUnlinking)
)(async () => {
authStore.update(_.set('nihStatusLoaded', false));
await Ajax().User.unlinkNihAccount();
await User().unlinkNihAccount();
authStore.update((oldState: AuthState) => ({
...oldState,
nihStatus: undefined,
Expand Down
126 changes: 60 additions & 66 deletions src/profile/external-identities/OAuth2Account.test.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { DeepPartial } from '@terra-ui-packages/core-utils';
import { act, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { axe } from 'jest-axe';
import React from 'react';
import { Ajax } from 'src/libs/ajax';
import { ExternalCredentials, ExternalCredentialsContract } from 'src/libs/ajax/ExternalCredentials';
import { Metrics, MetricsContract } from 'src/libs/ajax/Metrics';
import Events from 'src/libs/events';
import { getCurrentRoute } from 'src/libs/nav';
import { authStore } from 'src/libs/state';
import * as Utils from 'src/libs/utils';
import { OAuth2Provider } from 'src/profile/external-identities/OAuth2Providers';
import { asMockedFn, renderWithAppContexts as render } from 'src/testing/test-utils';
import { asMockedFn, MockedFn, partial, renderWithAppContexts as render } from 'src/testing/test-utils';

import { OAuth2Account } from './OAuth2Account';

jest.mock('src/libs/ajax');
jest.mock('src/libs/ajax/ExternalCredentials');
jest.mock('src/libs/ajax/Metrics');

jest.mock('src/auth/auth', () => ({
...jest.requireActual('src/auth/auth'),
Expand Down Expand Up @@ -45,9 +46,6 @@ jest.mock(
})
);

type AjaxExports = typeof import('src/libs/ajax');
type AjaxContract = ReturnType<AjaxExports['Ajax']>;

const testAccessTokenProvider: OAuth2Provider = {
key: 'github',
name: 'Test Provider',
Expand All @@ -64,19 +62,17 @@ describe('OAuth2Account', () => {
describe('When no account is linked', () => {
it('shows the button to link an account', async () => {
// Arrange
const getLinkStatusFn = jest.fn().mockResolvedValue(undefined);
const getAuthorizationUrlFn = jest.fn().mockResolvedValue('https://foo.bar.com/oauth2/authorize');
asMockedFn(Ajax).mockImplementation(
() =>
({
ExternalCredentials: () => {
return {
getAccountLinkStatus: getLinkStatusFn,
getAuthorizationUrl: getAuthorizationUrlFn,
};
},
} as DeepPartial<AjaxContract> as AjaxContract)
const getLinkStatusFn: MockedFn<ExternalCredentialsContract['getAccountLinkStatus']> = jest.fn();
getLinkStatusFn.mockResolvedValue(undefined);
const getAuthorizationUrlFn: MockedFn<ExternalCredentialsContract['getAuthorizationUrl']> = jest.fn();
getAuthorizationUrlFn.mockResolvedValue('https://foo.bar.com/oauth2/authorize');
asMockedFn(ExternalCredentials).mockReturnValue(() =>
partial<ExternalCredentialsContract>({
getAccountLinkStatus: getLinkStatusFn,
getAuthorizationUrl: getAuthorizationUrlFn,
})
);

// Act
const { container } = await act(() =>
render(<OAuth2Account queryParams={{}} provider={testAccessTokenProvider} />)
Expand All @@ -93,19 +89,19 @@ describe('OAuth2Account', () => {
// Arrange
const user = userEvent.setup();
jest.spyOn(window, 'open').mockImplementation(() => null);
const getLinkStatusFn = jest.fn().mockResolvedValue(undefined);
const getAuthorizationUrlFn = jest.fn().mockResolvedValue('https://foo.bar.com/oauth2/authorize');
asMockedFn(Ajax).mockImplementation(
() =>
({
ExternalCredentials: () => {
return {
getAccountLinkStatus: getLinkStatusFn,
getAuthorizationUrl: getAuthorizationUrlFn,
};
},
} as DeepPartial<AjaxContract> as AjaxContract)

const getLinkStatusFn: MockedFn<ExternalCredentialsContract['getAccountLinkStatus']> = jest.fn();
getLinkStatusFn.mockResolvedValue(undefined);
const getAuthorizationUrlFn: MockedFn<ExternalCredentialsContract['getAuthorizationUrl']> = jest.fn();
getAuthorizationUrlFn.mockResolvedValue('https://foo.bar.com/oauth2/authorize');

asMockedFn(ExternalCredentials).mockReturnValue(() =>
partial<ExternalCredentialsContract>({
getAccountLinkStatus: getLinkStatusFn,
getAuthorizationUrl: getAuthorizationUrlFn,
})
);

// Act
await act(() => render(<OAuth2Account queryParams={{}} provider={testAccessTokenProvider} />));

Expand All @@ -122,28 +118,28 @@ describe('OAuth2Account', () => {
oauthcode: 'abcde12345',
state: btoa(JSON.stringify({ provider: testAccessTokenProvider.key, nonce: 'abcxyz' })),
};
const getLinkStatusFn = jest.fn().mockResolvedValue(undefined);
const getAuthorizationUrlFn = jest.fn().mockResolvedValue('https://foo.bar.com/oauth2/authorize');
const linkAccountFn = jest
.fn()
.mockResolvedValue({ externalUserId: 'testUser', expirationTimestamp: new Date(), authenticated: true });
const captureEventFn = jest.fn();

asMockedFn(Ajax).mockImplementation(
() =>
({
ExternalCredentials: () => {
return {
getAccountLinkStatus: getLinkStatusFn,
getAuthorizationUrl: getAuthorizationUrlFn,
linkAccountWithAuthorizationCode: linkAccountFn,
};
},
Metrics: {
captureEvent: captureEventFn,
},
} as DeepPartial<AjaxContract> as AjaxContract)

const getLinkStatusFn: MockedFn<ExternalCredentialsContract['getAccountLinkStatus']> = jest.fn();
getLinkStatusFn.mockResolvedValue(undefined);
const getAuthorizationUrlFn: MockedFn<ExternalCredentialsContract['getAuthorizationUrl']> = jest.fn();
getAuthorizationUrlFn.mockResolvedValue('https://foo.bar.com/oauth2/authorize');
const linkAccountFn: MockedFn<ExternalCredentialsContract['linkAccountWithAuthorizationCode']> = jest.fn();
linkAccountFn.mockResolvedValue({
externalUserId: 'testUser',
expirationTimestamp: new Date(),
authenticated: true,
});
const captureEventFn: MockedFn<MetricsContract['captureEvent']> = jest.fn();

asMockedFn(ExternalCredentials).mockReturnValue(() =>
partial<ExternalCredentialsContract>({
getAccountLinkStatus: getLinkStatusFn,
getAuthorizationUrl: getAuthorizationUrlFn,
linkAccountWithAuthorizationCode: linkAccountFn,
})
);
asMockedFn(Metrics).mockReturnValue(partial<MetricsContract>({ captureEvent: captureEventFn }));

// Act
await act(() => render(<OAuth2Account queryParams={queryParams} provider={testAccessTokenProvider} />));

Expand All @@ -170,26 +166,24 @@ describe('OAuth2Account', () => {
screen.getByText('Link Expiration:');
screen.getByText(Utils.makeCompleteDate(linkStatus.expirationTimestamp));
});

it("unlinks the account when 'Unlink' is clicked", async () => {
// Arrange
const user = userEvent.setup();
const linkStatus = { externalUserId: 'testUser', expirationTimestamp: new Date(), authenticated: true };
authStore.update((state) => ({ ...state, oAuth2AccountStatus: { [testAccessTokenProvider.key]: linkStatus } }));
const unlinkAccountFn = jest.fn().mockResolvedValue(undefined);
const captureEventFn = jest.fn();
asMockedFn(Ajax).mockImplementation(
() =>
({
ExternalCredentials: () => {
return {
unlinkAccount: unlinkAccountFn,
};
},
Metrics: {
captureEvent: captureEventFn,
},
} as DeepPartial<AjaxContract> as AjaxContract)

const unlinkAccountFn: MockedFn<ExternalCredentialsContract['unlinkAccount']> = jest.fn();
unlinkAccountFn.mockResolvedValue(undefined);
const captureEventFn: MockedFn<MetricsContract['captureEvent']> = jest.fn();

asMockedFn(ExternalCredentials).mockReturnValue(() =>
partial<ExternalCredentialsContract>({
unlinkAccount: unlinkAccountFn,
})
);
asMockedFn(Metrics).mockReturnValue(partial<MetricsContract>({ captureEvent: captureEventFn }));

// Act
const { container } = await act(() =>
render(<OAuth2Account queryParams={{}} provider={testAccessTokenProvider} />)
Expand Down
11 changes: 5 additions & 6 deletions src/profile/external-identities/OAuth2Account.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import _ from 'lodash/fp';
import React, { useState } from 'react';
import { ClipboardButton } from 'src/components/ClipboardButton';
import { Ajax } from 'src/libs/ajax';
import { ExternalCredentials } from 'src/libs/ajax/ExternalCredentials';
import { Metrics } from 'src/libs/ajax/Metrics';
import colors from 'src/libs/colors';
import { withErrorReporting } from 'src/libs/error';
import Events from 'src/libs/events';
Expand Down Expand Up @@ -76,11 +77,9 @@ export const OAuth2Account = (props: OAuth2AccountProps) => {

useOnMount(() => {
const linkAccount = withErrorReporting(`Error linking ${provider.short} account`)(async (code, state) => {
const accountInfo = await Ajax(signal)
.ExternalCredentials(provider)
.linkAccountWithAuthorizationCode(code, state);
const accountInfo = await ExternalCredentials(signal)(provider).linkAccountWithAuthorizationCode(code, state);
authStore.update(_.set(['oAuth2AccountStatus', provider.key], accountInfo));
Ajax().Metrics.captureEvent(Events.user.externalCredential.link, { provider: provider.key });
void Metrics().captureEvent(Events.user.externalCredential.link, { provider: provider.key });
setIsLinking(false);
});

Expand Down Expand Up @@ -117,7 +116,7 @@ export const OAuth2Account = (props: OAuth2AccountProps) => {
<UnlinkOAuth2Account linkText='Unlink' provider={provider} />
</div>
{provider.supportsIdToken && (
<ClipboardButton text={Ajax(signal).ExternalCredentials(provider).getIdentityToken}>
<ClipboardButton text={ExternalCredentials(signal)(provider).getIdentityToken}>
Copy identity token to clipboard
</ClipboardButton>
)}
Expand Down
7 changes: 4 additions & 3 deletions src/profile/external-identities/UnlinkOAuth2Account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { Clickable, ClickableProps, Modal } from '@terra-ui-packages/components'
import _ from 'lodash/fp';
import React, { ReactNode, useState } from 'react';
import { ButtonPrimary, spinnerOverlay } from 'src/components/common';
import { Ajax } from 'src/libs/ajax';
import { ExternalCredentials } from 'src/libs/ajax/ExternalCredentials';
import { Metrics } from 'src/libs/ajax/Metrics';
import colors from 'src/libs/colors';
import { withErrorReporting } from 'src/libs/error';
import Events from 'src/libs/events';
Expand Down Expand Up @@ -34,9 +35,9 @@ export const UnlinkOAuth2Account = ({ linkText, provider }: UnlinkOAuth2AccountP
withErrorReporting('Error unlinking account'),
Utils.withBusyState(setIsUnlinking)
)(async () => {
await Ajax().ExternalCredentials(provider).unlinkAccount();
await ExternalCredentials()(provider).unlinkAccount();
authStore.update(_.unset(['oAuth2AccountStatus', provider.key]));
Ajax().Metrics.captureEvent(Events.user.externalCredential.unlink, { provider: provider.key });
void Metrics().captureEvent(Events.user.externalCredential.unlink, { provider: provider.key });
setIsModalOpen(false);
notify('success', 'Successfully unlinked account', {
message: `Successfully unlinked your account from ${provider.name}`,
Expand Down
Loading

0 comments on commit 251a323

Please sign in to comment.