Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[No QA] Follow-up: add unit tests for broken feed connection utility functions #55866

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
37 changes: 37 additions & 0 deletions src/libs/CardUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,41 @@ function getFeedType(feedKey: CompanyCardFeed, cardFeeds: OnyxEntry<CardFeeds>):
return feedKey;
}

/**
* Takes the list of cards divided by workspaces and feeds and returns the flattened non-Expensify cards related to the provided workspace
*
* @param allCardsList the list where cards split by workspaces and feeds and stored under `card_${workspaceAccountID}_${feedName}` keys
* @param workspaceAccountID the workspace account id we want to get cards for
*/
function flatAllCardsList(allCardsList: OnyxCollection<WorkspaceCardsList>, workspaceAccountID: number): Record<string, Card> | undefined {
if (!allCardsList) {
return;
}

return Object.entries(allCardsList).reduce((acc, [key, cards]) => {
if (!key.includes(workspaceAccountID.toString()) || key.includes(CONST.EXPENSIFY_CARD.BANK)) {
return acc;
}
const {cardList, ...feedCards} = cards ?? {};
Object.assign(acc, feedCards);
return acc;
}, {});
}

/**
* Check if any card from the provided feed(s) has a broken connection
*
* @param feedCards the list of the cards, related to one or several feeds
* @param [feedToExclude] the feed to ignore during the check, it's useful for checking broken connection error only in the feeds other than the selected one
*/
function checkIfFeedConnectionIsBroken(feedCards: Record<string, Card> | undefined, feedToExclude?: string): boolean {
if (!feedCards || isEmptyObject(feedCards)) {
return false;
}

return Object.values(feedCards).some((card) => card.bank !== feedToExclude && card.lastScrapeResult !== 200);
}

export {
isExpensifyCard,
isCorporateCard,
Expand Down Expand Up @@ -504,4 +539,6 @@ export {
isCardIssued,
isCardHiddenFromSearch,
getFeedType,
flatAllCardsList,
checkIfFeedConnectionIsBroken,
};
42 changes: 2 additions & 40 deletions src/libs/actions/CompanyCards.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx';
import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import * as API from '@libs/API';
import type {
Expand All @@ -18,11 +18,10 @@ import * as PolicyUtils from '@libs/PolicyUtils';
import * as ReportUtils from '@libs/ReportUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Card, CardFeeds, WorkspaceCardsList} from '@src/types/onyx';
import type {Card, CardFeeds} from '@src/types/onyx';
import type {AssignCard, AssignCardData} from '@src/types/onyx/AssignCard';
import type {AddNewCardFeedData, AddNewCardFeedStep, CompanyCardFeed} from '@src/types/onyx/CardFeeds';
import type {OnyxData} from '@src/types/onyx/Request';
import {isEmptyObject} from '@src/types/utils/EmptyObject';

type AddNewCompanyCardFlowData = {
/** Step to be set in Onyx */
Expand Down Expand Up @@ -725,41 +724,6 @@ function openPolicyCompanyCardsFeed(policyID: string, feed: CompanyCardFeed) {
API.read(READ_COMMANDS.OPEN_POLICY_COMPANY_CARDS_FEED, parameters);
}

/**
* Takes the list of cards divided by workspaces and feeds and returns the flattened non-Expensify cards related to the provided workspace
*
* @param allCardsList the list where cards split by workspaces and feeds and stored under `card_${workspaceAccountID}_${feedName}` keys
* @param workspaceAccountID the workspace account id we want to get cards for
*/
function flatAllCardsList(allCardsList: OnyxCollection<WorkspaceCardsList>, workspaceAccountID: number): Record<string, Card> | undefined {
if (!allCardsList) {
return;
}

return Object.entries(allCardsList).reduce((acc, [key, allCards]) => {
if (!key.includes(workspaceAccountID.toString()) || key.includes(CONST.EXPENSIFY_CARD.BANK)) {
return acc;
}
const {cardList, ...feedCards} = allCards ?? {};
Object.assign(acc, feedCards);
return acc;
}, {});
}

/**
* Check if any feed card has a broken connection
*
* @param feedCards the list of the cards, related to one or several feeds
* @param [feedToExclude] the feed to ignore during the check, it's useful for checking broken connection error only in the feeds other than the selected one
*/
function checkIfFeedConnectionIsBroken(feedCards: Record<string, Card> | undefined, feedToExclude?: string): boolean {
if (!feedCards || isEmptyObject(feedCards)) {
return false;
}

return Object.values(feedCards).some((card) => card.bank !== feedToExclude && card.lastScrapeResult !== 200);
}

export {
setWorkspaceCompanyCardFeedName,
deleteWorkspaceCompanyCardFeed,
Expand All @@ -777,6 +741,4 @@ export {
clearAddNewCardFlow,
setAssignCardStepAndData,
clearAssignCardStepAndData,
checkIfFeedConnectionIsBroken,
flatAllCardsList,
};
2 changes: 1 addition & 1 deletion src/pages/workspace/WorkspaceInitialPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import useWaitForNavigation from '@hooks/useWaitForNavigation';
import {isConnectionInProgress} from '@libs/actions/connections';
import {clearErrors, openPolicyInitialPage, removeWorkspace, updateGeneralSettings} from '@libs/actions/Policy/Policy';
import {navigateToBankAccountRoute} from '@libs/actions/ReimbursementAccount';
import {checkIfFeedConnectionIsBroken, flatAllCardsList} from '@libs/CardUtils';
import {convertToDisplayString} from '@libs/CurrencyUtils';
import getTopmostRouteName from '@libs/Navigation/getTopmostRouteName';
import Navigation from '@libs/Navigation/Navigation';
Expand All @@ -43,7 +44,6 @@ import {
import {getDefaultWorkspaceAvatar, getIcons, getPolicyExpenseChat, getReportName, getReportOfflinePendingActionAndErrors} from '@libs/ReportUtils';
import type {FullScreenNavigatorParamList} from '@navigation/types';
import {confirmReadyToOpenApp} from '@userActions/App';
import {checkIfFeedConnectionIsBroken, flatAllCardsList} from '@userActions/CompanyCards';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import RadioListItem from '@components/SelectionList/RadioListItem';
import type {ListItem} from '@components/SelectionList/types';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import {getCardFeedIcon, getCompanyFeeds, getCustomOrFormattedFeedName, getSelectedFeed} from '@libs/CardUtils';
import {checkIfFeedConnectionIsBroken, getCardFeedIcon, getCompanyFeeds, getCustomOrFormattedFeedName, getSelectedFeed} from '@libs/CardUtils';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
import {getWorkspaceAccountID} from '@libs/PolicyUtils';
import Navigation from '@navigation/Navigation';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import variables from '@styles/variables';
import {updateSelectedFeed} from '@userActions/Card';
import {checkIfFeedConnectionIsBroken, clearAddNewCardFlow} from '@userActions/CompanyCards';
import {clearAddNewCardFlow} from '@userActions/CompanyCards';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {getBankName, getCardFeedIcon, getCompanyFeeds, getCustomOrFormattedFeedName, isCustomFeed} from '@libs/CardUtils';
import {checkIfFeedConnectionIsBroken, flatAllCardsList, getBankName, getCardFeedIcon, getCompanyFeeds, getCustomOrFormattedFeedName, isCustomFeed} from '@libs/CardUtils';
import {getWorkspaceAccountID} from '@libs/PolicyUtils';
import Navigation from '@navigation/Navigation';
import variables from '@styles/variables';
import {checkIfFeedConnectionIsBroken, flatAllCardsList} from '@userActions/CompanyCards';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {CompanyCardFeed} from '@src/types/onyx';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {getCompanyFeeds, getFilteredCardList, getSelectedFeed, hasOnlyOneCardToAssign, isSelectedFeedExpired} from '@libs/CardUtils';
import {checkIfFeedConnectionIsBroken, getCompanyFeeds, getFilteredCardList, getSelectedFeed, hasOnlyOneCardToAssign, isSelectedFeedExpired} from '@libs/CardUtils';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {FullScreenNavigatorParamList} from '@libs/Navigation/types';
import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils';
import {getWorkspaceAccountID, isDeletedPolicyEmployee} from '@libs/PolicyUtils';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections';
import {checkIfFeedConnectionIsBroken, openPolicyCompanyCardsFeed, openPolicyCompanyCardsPage, setAssignCardStepAndData} from '@userActions/CompanyCards';
import {openPolicyCompanyCardsFeed, openPolicyCompanyCardsPage, setAssignCardStepAndData} from '@userActions/CompanyCards';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
Expand Down
64 changes: 64 additions & 0 deletions tests/unit/CardUtilsTest.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {OnyxCollection} from 'react-native-onyx';
import CONST from '@src/CONST';
import * as CardUtils from '@src/libs/CardUtils';
import {checkIfFeedConnectionIsBroken, flatAllCardsList} from '@src/libs/CardUtils';
import type * as OnyxTypes from '@src/types/onyx';
import type {CompanyCardFeedWithNumber} from '@src/types/onyx/CardFeeds';

Expand Down Expand Up @@ -102,6 +103,7 @@ const directFeedCardsSingleList: OnyxTypes.WorkspaceCardsList = {
lastFourPAN: '5501',
lastScrape: '',
lastUpdated: '',
lastScrapeResult: 200,
scrapeMinDate: '2024-08-27',
state: 3,
},
Expand All @@ -118,6 +120,7 @@ const directFeedCardsMultipleList: OnyxTypes.WorkspaceCardsList = {
lastFourPAN: '5678',
lastScrape: '',
lastUpdated: '',
lastScrapeResult: 200,
scrapeMinDate: '2024-08-27',
state: 3,
},
Expand All @@ -132,6 +135,7 @@ const directFeedCardsMultipleList: OnyxTypes.WorkspaceCardsList = {
lastFourPAN: '5678',
lastScrape: '',
lastUpdated: '',
lastScrapeResult: 403,
scrapeMinDate: '2024-08-27',
state: 3,
},
Expand Down Expand Up @@ -200,6 +204,28 @@ const cardFeedsCollection: OnyxCollection<OnyxTypes.CardFeeds> = {
},
};

/* eslint-disable @typescript-eslint/naming-convention */
const allCardsList = {
'cards_11111111_oauth.capitalone.com': directFeedCardsMultipleList,
cards_11111111_vcf: customFeedCardsList,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this on purpose not a string?

Also lets test vcf1 since that is what we use now

Suggested change
cards_11111111_vcf: customFeedCardsList,
'cards_11111111_vcf1': customFeedCardsList,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this on purpose not a string?

Yeah, there is a prettier error
image

Also lets test vcf1 since that is what we use now

Updated!

'cards_22222222_oauth.chase.com': directFeedCardsSingleList,
'cards_11111111_Expensify Card': {
'21570657': {
accountID: 18439984,
bank: CONST.EXPENSIFY_CARD.BANK,
cardID: 21570657,
cardName: 'CREDIT CARD...5644',
domainName: 'expensify-policya7f617b9fe23d2f1.exfy',
fraud: 'none',
lastFourPAN: '',
lastScrape: '',
lastUpdated: '',
state: 2,
},
},
} as OnyxCollection<OnyxTypes.WorkspaceCardsList>;
/* eslint-enable @typescript-eslint/naming-convention */

describe('CardUtils', () => {
describe('Expiration date formatting', () => {
it('Should format expirationDate month and year to MM/YYYY', () => {
Expand Down Expand Up @@ -472,4 +498,42 @@ describe('CardUtils', () => {
expect(feedType).toBe('vcf2');
});
});

describe('flatAllCardsList', () => {
it('should return the flattened list of non-Expensify cards related to the provided workspaceAccountID', () => {
const workspaceAccountID = 11111111;
const flattenedCardsList = flatAllCardsList(allCardsList, workspaceAccountID);
const {cardList, ...customCards} = customFeedCardsList;
expect(flattenedCardsList).toStrictEqual({
...directFeedCardsMultipleList,
...customCards,
});
});

it('should return undefined if not defined cards list was provided', () => {
const workspaceAccountID = 11111111;
const flattenedCardsList = flatAllCardsList(undefined, workspaceAccountID);
expect(flattenedCardsList).toBeUndefined();
});
});

describe('checkIfFeedConnectionIsBroken', () => {
it('should return true if at least one of the feed(s) cards has the lastScrapeResult not equal to 200', () => {
expect(checkIfFeedConnectionIsBroken(directFeedCardsMultipleList)).toBeTruthy();
});

it('should return false if all of the feed(s) cards has the lastScrapeResult equal to 200', () => {
expect(checkIfFeedConnectionIsBroken(directFeedCardsSingleList)).toBeFalsy();
});

it('should return false if no feed(s) cards are provided', () => {
expect(checkIfFeedConnectionIsBroken({})).toBeFalsy();
});

it('should not take into consideration cards related to feed which is provided as feedToExclude', () => {
const cards = {...directFeedCardsMultipleList, ...directFeedCardsSingleList};
const feedToExclude = CONST.COMPANY_CARD.FEED_BANK_NAME.CAPITAL_ONE;
expect(checkIfFeedConnectionIsBroken(cards, feedToExclude)).toBeFalsy();
});
});
});
Loading