Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2552,7 +2552,6 @@ const translations: TranslationDeepObject<typeof en> = {
verifyNewDeviceDescription: 'Scannen Sie den QR-Code mit Ihrem neuen Gerät und geben Sie dann den Code ein, um die Einrichtung abzuschließen.',
downloadCodes: 'Codes herunterladen',
copyCodes: 'Codes kopieren',
twoFactorAuthIsRequiredNetSuiteDescription: 'Aus Sicherheitsgründen erfordert NetSuite zur Verbindung der Integration eine Zwei-Faktor-Authentifizierung.',
},
recoveryCodeForm: {
error: {
Expand Down
1 change: 0 additions & 1 deletion src/languages/el.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2608,7 +2608,6 @@ const translations: TranslationDeepObject<typeof en> = {
verifyOldDeviceDescriptionWithRecovery: 'Εισαγάγετε έναν έγκυρο κωδικό ανάκτησης για να επιβεβαιώσετε ότι έχετε πρόσβαση στον λογαριασμό σας.',
verifyNewDeviceTitle: 'Ρύθμιση νέας συσκευής',
verifyNewDeviceDescription: 'Σαρώστε τον κωδικό QR με τη νέα σας συσκευή και κατόπιν εισαγάγετε τον κωδικό για να ολοκληρώσετε τη ρύθμιση.',
twoFactorAuthIsRequiredNetSuiteDescription: 'Για λόγους ασφαλείας, το NetSuite απαιτεί έλεγχο ταυτότητας δύο παραγόντων για τη σύνδεση της ενοποίησης.',
},
recoveryCodeForm: {
error: {
Expand Down
1 change: 0 additions & 1 deletion src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2646,7 +2646,6 @@ const translations = {
enableTwoFactorAuth: 'Enable two-factor authentication',
pleaseEnableTwoFactorAuth: 'Please enable two-factor authentication.',
twoFactorAuthIsRequiredDescription: 'For security purposes, Xero requires two-factor authentication to connect the integration.',
twoFactorAuthIsRequiredNetSuiteDescription: 'For security purposes, NetSuite requires two-factor authentication to connect the integration.',
twoFactorAuthIsRequiredForAdminsHeader: 'Two-factor authentication required',
twoFactorAuthIsRequiredForAdminsTitle: 'Please enable two-factor authentication',
twoFactorAuthIsRequiredXero: 'Your Xero accounting connection requires two-factor authentication.',
Expand Down
1 change: 0 additions & 1 deletion src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2472,7 +2472,6 @@ const translations: TranslationDeepObject<typeof en> = {
verifyNewDeviceDescription: 'Escanea el código QR con tu nuevo dispositivo y luego introduce el código para completar la configuración.',
downloadCodes: 'Descargar códigos',
copyCodes: 'Copiar códigos',
twoFactorAuthIsRequiredNetSuiteDescription: 'Por motivos de seguridad, NetSuite requiere autenticación de dos factores para conectar la integración.',
},
recoveryCodeForm: {
error: {
Expand Down
1 change: 0 additions & 1 deletion src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2556,7 +2556,6 @@ const translations: TranslationDeepObject<typeof en> = {
verifyNewDeviceDescription: 'Scannez le code QR avec votre nouvel appareil, puis saisissez le code pour terminer la configuration.',
downloadCodes: 'Télécharger les codes',
copyCodes: 'Copier les codes',
twoFactorAuthIsRequiredNetSuiteDescription: "Pour des raisons de sécurité, NetSuite exige une authentification à deux facteurs pour connecter l'intégration.",
},
recoveryCodeForm: {
error: {
Expand Down
1 change: 0 additions & 1 deletion src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2543,7 +2543,6 @@ const translations: TranslationDeepObject<typeof en> = {
verifyNewDeviceDescription: 'Scansiona il codice QR con il tuo nuovo dispositivo, poi inserisci il codice per completare la configurazione.',
downloadCodes: 'Scarica codici',
copyCodes: 'Copia codici',
twoFactorAuthIsRequiredNetSuiteDescription: 'Per motivi di sicurezza, NetSuite richiede l’autenticazione a due fattori per collegare l’integrazione.',
},
recoveryCodeForm: {
error: {
Expand Down
1 change: 0 additions & 1 deletion src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2525,7 +2525,6 @@ const translations: TranslationDeepObject<typeof en> = {
verifyNewDeviceDescription: '新しいデバイスでQRコードをスキャンし、表示されたコードを入力して設定を完了してください。',
downloadCodes: 'コードをダウンロード',
copyCodes: 'コードをコピー',
twoFactorAuthIsRequiredNetSuiteDescription: 'セキュリティ上の理由により、連携を接続するには NetSuite で二要素認証が必要です。',
},
recoveryCodeForm: {
error: {
Expand Down
1 change: 0 additions & 1 deletion src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2544,7 +2544,6 @@ const translations: TranslationDeepObject<typeof en> = {
verifyNewDeviceDescription: 'Scan de QR-code met je nieuwe apparaat en voer daarna de code in om de installatie te voltooien.',
downloadCodes: 'Codes downloaden',
copyCodes: 'Codes kopiëren',
twoFactorAuthIsRequiredNetSuiteDescription: 'Om veiligheidsredenen vereist NetSuite tweeledige verificatie om de koppeling te maken.',
},
recoveryCodeForm: {
error: {
Expand Down
1 change: 0 additions & 1 deletion src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2581,7 +2581,6 @@ const translations: TranslationDeepObject<typeof en> = {
verifyNewDeviceDescription: 'Zeskanuj kod QR nowym urządzeniem, a następnie wpisz ten kod, aby zakończyć konfigurację.',
downloadCodes: 'Pobierz kody',
copyCodes: 'Skopiuj kody',
twoFactorAuthIsRequiredNetSuiteDescription: 'Ze względów bezpieczeństwa NetSuite wymaga uwierzytelniania dwuskładnikowego, aby połączyć integrację.',
},
recoveryCodeForm: {
error: {
Expand Down
1 change: 0 additions & 1 deletion src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2537,7 +2537,6 @@ const translations: TranslationDeepObject<typeof en> = {
verifyNewDeviceDescription: 'Escaneie o código QR com seu novo dispositivo e depois insira o código para concluir a configuração.',
downloadCodes: 'Baixar códigos',
copyCodes: 'Copiar códigos',
twoFactorAuthIsRequiredNetSuiteDescription: 'Por motivos de segurança, o NetSuite exige autenticação em duas etapas para conectar a integração.',
},
recoveryCodeForm: {
error: {
Expand Down
1 change: 0 additions & 1 deletion src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2460,7 +2460,6 @@ const translations: TranslationDeepObject<typeof en> = {
verifyNewDeviceDescription: '使用新设备扫描二维码,然后输入代码完成设置。',
downloadCodes: '下载代码',
copyCodes: '复制代码',
twoFactorAuthIsRequiredNetSuiteDescription: '出于安全考虑,NetSuite 要求使用双重身份验证来连接此集成。',
},
recoveryCodeForm: {
error: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
import RenderHTML from '@components/RenderHTML';
import RequireTwoFactorAuthenticationModal from '@components/RequireTwoFactorAuthenticationModal';
import Text from '@components/Text';
import TextInput from '@components/TextInput';

Expand All @@ -11,13 +10,11 @@ import useEnvironment from '@hooks/useEnvironment';
import useLocalize from '@hooks/useLocalize';
import usePolicy from '@hooks/usePolicy';
import useThemeStyles from '@hooks/useThemeStyles';
import useTwoFactorAuthRoute from '@hooks/useTwoFactorAuthRoute';

import {shouldUseUpdateNetSuiteTokens} from '@libs/actions/connections';
import {connectPolicyToNetSuite, updateNetSuiteTokens} from '@libs/actions/connections/NetSuiteCommands';
import {isMobileSafari} from '@libs/Browser';
import {addErrorMessage} from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import Parser from '@libs/Parser';

import NetSuiteTokenAuthenticationLink from '@pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenAuthenticationLink';
Expand All @@ -27,7 +24,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import INPUT_IDS from '@src/types/form/NetSuiteTokenInputForm';

import React, {useCallback, useState} from 'react';
import React, {useCallback} from 'react';
import {View} from 'react-native';

import connectToNetSuiteOAuthSetup from './connectToNetSuiteOAuthSetup';
Expand All @@ -38,9 +35,6 @@ function NetSuiteTokenInputForm({onNext, policyID, isOAuthFlow, shouldShowTokenA
const policy = usePolicy(policyID);
const {inputCallbackRef} = useAutoFocusInput();
const {environmentURL} = useEnvironment();
const {is2FAEnabled, getTwoFactorAuthRoute} = useTwoFactorAuthRoute();

const [isRequire2FAModalOpen, setIsRequire2FAModalOpen] = useState(false);

const formInputs = isOAuthFlow ? [INPUT_IDS.NETSUITE_ACCOUNT_ID] : Object.values(INPUT_IDS);

Expand All @@ -66,10 +60,6 @@ function NetSuiteTokenInputForm({onNext, policyID, isOAuthFlow, shouldShowTokenA
}

if (isOAuthFlow) {
if (!is2FAEnabled) {
setIsRequire2FAModalOpen(true);
return;
}
connectToNetSuiteOAuthSetup(policyID, formValues[INPUT_IDS.NETSUITE_ACCOUNT_ID], environmentURL);
return;
}
Expand All @@ -81,7 +71,7 @@ function NetSuiteTokenInputForm({onNext, policyID, isOAuthFlow, shouldShowTokenA
}
onNext();
},
[onNext, policyID, policy, isOAuthFlow, is2FAEnabled, environmentURL],
[onNext, policyID, policy, isOAuthFlow, environmentURL],
);

return (
Expand Down Expand Up @@ -126,19 +116,6 @@ function NetSuiteTokenInputForm({onNext, policyID, isOAuthFlow, shouldShowTokenA
</View>
))}
</FormProvider>
{isOAuthFlow && !is2FAEnabled && (
<RequireTwoFactorAuthenticationModal
onSubmit={() => {
setIsRequire2FAModalOpen(false);
Navigation.navigate(getTwoFactorAuthRoute());
}}
onCancel={() => {
setIsRequire2FAModalOpen(false);
}}
isVisible={isRequire2FAModalOpen}
description={translate('twoFactorAuth.twoFactorAuthIsRequiredNetSuiteDescription')}
/>
)}
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,44 +1,29 @@
import {act, render, screen} from '@testing-library/react-native';

import useTwoFactorAuthRoute from '@hooks/useTwoFactorAuthRoute';
import {act, render} from '@testing-library/react-native';

import {shouldUseUpdateNetSuiteTokens} from '@libs/actions/connections';
import {connectPolicyToNetSuite, updateNetSuiteTokens} from '@libs/actions/connections/NetSuiteCommands';
import Navigation from '@libs/Navigation/Navigation';

import connectToNetSuiteOAuthSetup from '@pages/workspace/accounting/netsuite/NetSuiteTokenInput/subPages/connectToNetSuiteOAuthSetup';
import NetSuiteTokenInputForm from '@pages/workspace/accounting/netsuite/NetSuiteTokenInput/subPages/NetSuiteTokenInputForm';

import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type {NetSuiteTokenInputForm as NetSuiteTokenInputFormType} from '@src/types/form/NetSuiteTokenInputForm';

import React from 'react';
import {View} from 'react-native';

const POLICY_ID = '123';
const ACCOUNT_ID = 'TSTDRV1234567';
const TOKEN_ID = 'token-123';
const TOKEN_SECRET = 'secret-123';
const ENVIRONMENT_URL = 'https://new.expensify.com';
const TWO_FACTOR_AUTH_ROUTE = ROUTES.SETTINGS_2FA_ENABLED;

const FORM_VALUES = {
netSuiteAccountID: ACCOUNT_ID,
netSuiteTokenID: TOKEN_ID,
netSuiteTokenSecret: TOKEN_SECRET,
} as NetSuiteTokenInputFormType;

type Require2FAProps = {
isVisible: boolean;
onSubmit: () => void;
onCancel: () => void;
};

// `Mock`-prefixed bindings are allowed inside jest.mock factories, unlike regular imports.
const MockView = View;

// Capture the props of the mocked children so the tests can drive submission and the modal.
// Capture the form props to submit credentials in tests.
type MockFormProviderProps = {
children: React.ReactNode;
onSubmit: (values: NetSuiteTokenInputFormType) => void;
Expand All @@ -50,9 +35,6 @@ type MockFormProviderProps = {
const mockFormProps: {current: Omit<MockFormProviderProps, 'children'> | undefined} = {
current: undefined,
};
const mockRequire2FAProps: {current: Require2FAProps | undefined} = {
current: undefined,
};

jest.mock('@hooks/useLocalize', () => () => ({
translate: (key: string) => key,
Expand All @@ -75,7 +57,6 @@ jest.mock('@hooks/useAutoFocusInput', () => () => ({
inputCallbackRef: jest.fn(),
}));
jest.mock('@hooks/usePolicy', () => () => undefined);
jest.mock('@hooks/useTwoFactorAuthRoute');
jest.mock('@libs/actions/connections', () => ({
shouldUseUpdateNetSuiteTokens: jest.fn(() => false),
}));
Expand All @@ -84,9 +65,6 @@ jest.mock('@libs/actions/connections/NetSuiteCommands', () => ({
updateNetSuiteTokens: jest.fn(),
}));
jest.mock('@pages/workspace/accounting/netsuite/NetSuiteTokenInput/subPages/connectToNetSuiteOAuthSetup', () => jest.fn());
jest.mock('@libs/Navigation/Navigation', () => ({
navigate: jest.fn(),
}));
jest.mock('@components/RenderHTML', () => () => null);
jest.mock('@components/Form/FormProvider', () => {
function MockFormProvider({children, onSubmit, keyboardSubmitBehavior, shouldShowLoadingImmediatelyOnPress, submitButtonText}: MockFormProviderProps) {
Expand All @@ -96,27 +74,13 @@ jest.mock('@components/Form/FormProvider', () => {
return MockFormProvider;
});
jest.mock('@components/Form/InputWrapper', () => () => null);
jest.mock('@components/RequireTwoFactorAuthenticationModal', () => ({isVisible, onSubmit, onCancel}: Require2FAProps) => {
mockRequire2FAProps.current = {isVisible, onSubmit, onCancel};
return isVisible ? <MockView testID="require-2fa-modal" /> : null;
});

const mockedUseTwoFactorAuthRoute = jest.mocked(useTwoFactorAuthRoute);
const mockedShouldUseUpdateNetSuiteTokens = jest.mocked(shouldUseUpdateNetSuiteTokens);
const mockedConnectPolicyToNetSuite = jest.mocked(connectPolicyToNetSuite);
const mockedUpdateNetSuiteTokens = jest.mocked(updateNetSuiteTokens);
const mockedConnectToNetSuiteOAuthSetup = jest.mocked(connectToNetSuiteOAuthSetup);
const mockedNavigate = jest.mocked(Navigation.navigate);
const mockedGetTwoFactorAuthRoute = jest.fn(() => TWO_FACTOR_AUTH_ROUTE);
const mockedOnNext = jest.fn();

function set2FAEnabled(is2FAEnabled: boolean) {
mockedUseTwoFactorAuthRoute.mockReturnValue({
is2FAEnabled,
getTwoFactorAuthRoute: mockedGetTwoFactorAuthRoute,
});
}

function renderForm(isOAuthFlow: boolean) {
render(
<NetSuiteTokenInputForm
Expand All @@ -139,16 +103,10 @@ describe('NetSuiteTokenInputForm', () => {
beforeEach(() => {
jest.clearAllMocks();
mockFormProps.current = undefined;
mockRequire2FAProps.current = undefined;
mockedGetTwoFactorAuthRoute.mockReturnValue(TWO_FACTOR_AUTH_ROUTE);
mockedShouldUseUpdateNetSuiteTokens.mockReturnValue(false);
});

describe('in the OAuth flow with 2FA enabled', () => {
beforeEach(() => {
set2FAEnabled(true);
});

describe('in the OAuth flow', () => {
it('hands off to the OAuth setup with the policy, account ID and environment URL', () => {
renderForm(true);
submitForm();
Expand All @@ -171,13 +129,6 @@ describe('NetSuiteTokenInputForm', () => {
expect(mockedOnNext).not.toHaveBeenCalled();
});

it('does not show the 2FA requirement modal', () => {
renderForm(true);
submitForm();

expect(screen.queryByTestId('require-2fa-modal')).toBeNull();
});

it('submits synchronously so the setup link opens inside the tap gesture and is not popup-blocked', () => {
renderForm(true);

Expand All @@ -194,54 +145,13 @@ describe('NetSuiteTokenInputForm', () => {
});
});

describe('in the OAuth flow with 2FA not enabled', () => {
beforeEach(() => {
set2FAEnabled(false);
});

it('blocks the OAuth handoff and shows the 2FA requirement modal', () => {
renderForm(true);
submitForm();

expect(screen.getByTestId('require-2fa-modal')).toBeOnTheScreen();
expect(mockedConnectToNetSuiteOAuthSetup).not.toHaveBeenCalled();
});

it('navigates to the 2FA route when the modal is submitted', () => {
renderForm(true);
submitForm();
expect(screen.getByTestId('require-2fa-modal')).toBeOnTheScreen();

act(() => mockRequire2FAProps.current?.onSubmit());

expect(mockedGetTwoFactorAuthRoute).toHaveBeenCalled();
expect(mockedNavigate).toHaveBeenCalledWith(TWO_FACTOR_AUTH_ROUTE);
});

it('closes the modal without navigating when cancelled', () => {
renderForm(true);
submitForm();
expect(screen.getByTestId('require-2fa-modal')).toBeOnTheScreen();

act(() => mockRequire2FAProps.current?.onCancel());

expect(mockedNavigate).not.toHaveBeenCalled();
expect(screen.queryByTestId('require-2fa-modal')).toBeNull();
});
});

describe('in the token-based authentication flow', () => {
beforeEach(() => {
set2FAEnabled(false);
});

it('writes the token-based credentials without requiring 2FA', () => {
it('writes the token-based credentials', () => {
renderForm(false);
submitForm();

expect(mockedConnectPolicyToNetSuite).toHaveBeenCalledWith(POLICY_ID, FORM_VALUES);
expect(mockedConnectToNetSuiteOAuthSetup).not.toHaveBeenCalled();
expect(screen.queryByTestId('require-2fa-modal')).toBeNull();
});

it('advances the wizard so the RHP is dismissed', () => {
Expand Down
Loading