Skip to content

Commit

Permalink
Merge branch 'SIW-2072' into SIW-2036
Browse files Browse the repository at this point in the history
  • Loading branch information
mastro993 committed Mar 4, 2025
2 parents 8766f3b + 52d9e81 commit 2d57f56
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 20 deletions.
15 changes: 5 additions & 10 deletions ts/features/itwallet/common/saga/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,17 @@ import { SagaIterator } from "redux-saga";
import { call, fork } from "typed-redux-saga/macro";
import { watchItwCredentialsSaga } from "../../credentials/saga";
import { checkCredentialsStatusAttestation } from "../../credentials/saga/checkCredentialsStatusAttestation";
import { handleWalletCredentialsRehydration } from "../../credentials/saga/handleWalletCredentialsRehydration";
import { watchItwLifecycleSaga } from "../../lifecycle/saga";
import { warmUpIntegrityServiceSaga } from "../../lifecycle/saga/checkIntegrityServiceReadySaga";
import { checkWalletInstanceStateSaga } from "../../lifecycle/saga/checkWalletInstanceStateSaga";

function* checkWalletInstanceAndCredentialsValiditySaga() {
// Status attestations of credentials are checked only in case of a valid wallet instance.
// For this reason, these sagas must be called sequentially.
yield* call(checkWalletInstanceStateSaga);
yield* call(checkCredentialsStatusAttestation);
}

export function* watchItwSaga(): SagaIterator {
yield* fork(warmUpIntegrityServiceSaga);
yield* fork(checkWalletInstanceAndCredentialsValiditySaga);
yield* fork(handleWalletCredentialsRehydration);
yield* fork(watchItwCredentialsSaga);
yield* fork(watchItwLifecycleSaga);

// Status attestations of credentials are checked only in case of a valid wallet instance.
// For this reason, these sagas must be called sequentially.
yield* call(checkWalletInstanceStateSaga);
yield* call(checkCredentialsStatusAttestation);
}
4 changes: 1 addition & 3 deletions ts/features/itwallet/common/store/selectors/remoteConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ const itwRemoteConfigSelector = (state: GlobalState) =>
);

/**
* Return the remote config about IT-WALLET enabled/disabled
* if there is no data or the local Feature Flag is disabled,
* false is the default value -> (IT-WALLET disabled)
* Returns the remote config for IT-WALLET
*/
export const isItwEnabledSelector = createSelector(
itwRemoteConfigSelector,
Expand Down
7 changes: 4 additions & 3 deletions ts/features/itwallet/navigation/ItwStackNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { ItwOfflineWalletScreen } from "../wallet/screens/ItwOfflineWalletScreen
import { isItwEnabledSelector } from "../common/store/selectors/remoteConfig";
import { ItwGenericErrorContent } from "../common/components/ItwGenericErrorContent";
import { useIOSelector } from "../../../store/hooks";
import { isConnectedSelector } from "../../connectivity/store/selectors";
import { ItwParamsList } from "./ItwParamsList";
import { ITW_ROUTES } from "./routes";

Expand Down Expand Up @@ -259,10 +260,10 @@ const withItwEnabled =
<P extends Record<string, unknown>>(Screen: ComponentType<P>) =>
(props: P) => {
const isItwEnabled = useIOSelector(isItwEnabledSelector);
const isConnected = useIOSelector(isConnectedSelector);

if (!isItwEnabled) {
// In case the user lands in this screen and IT Wallet is not enabled,
// we should render an error screen.
// Show error content only if connected and IT Wallet is not enabled
if (isConnected && !isItwEnabled) {
return <ItwGenericErrorContent />;
}
return <Screen {...props} />;
Expand Down
131 changes: 130 additions & 1 deletion ts/features/wallet/store/selectors/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
selectWalletCardsByType,
selectWalletCategories,
shouldRenderWalletCategorySelector,
shouldRenderWalletEmptyStateSelector
shouldRenderWalletEmptyStateSelector,
shouldRenderItwCardsContainerSelector
} from "..";
import { applicationChangeState } from "../../../../../store/actions/application";
import { appReducer } from "../../../../../store/reducers";
Expand All @@ -19,6 +20,9 @@ import {
} from "../../../../itwallet/common/utils/itwMocksUtils";
import { ItwLifecycleState } from "../../../../itwallet/lifecycle/store/reducers";
import * as itwLifecycleSelectors from "../../../../itwallet/lifecycle/store/selectors";
import * as itwWalletInstanceSelectors from "../../../../itwallet/walletInstance/store/selectors";
import * as itwSelectors from "../../../../itwallet/common/store/selectors/remoteConfig";
import * as connectivitySelectors from "../../../../connectivity/store/selectors";
import { walletCardCategoryFilters } from "../../../types";
import { WalletCardsState } from "../../reducers/cards";

Expand Down Expand Up @@ -366,3 +370,128 @@ describe("shouldRenderWalletCategorySelector", () => {
}
);
});

describe("shouldRenderItwCardsContainerSelector", () => {
it("should return true when ITW is enabled, lifecycle is valid, and no WI error", () => {
const globalState = appReducer(undefined, applicationChangeState("active"));

jest
.spyOn(itwLifecycleSelectors, "itwLifecycleIsValidSelector")
.mockImplementation(() => true);

jest
.spyOn(
itwWalletInstanceSelectors,
"itwIsWalletInstanceStatusFailureSelector"
)
.mockImplementation(() => false);

jest
.spyOn(itwSelectors, "isItwEnabledSelector")
.mockImplementation(() => true);

const shouldRenderItwCardsContainer =
shouldRenderItwCardsContainerSelector(globalState);
expect(shouldRenderItwCardsContainer).toBe(true);
});

it("should return true when offline, lifecycle is valid, and no WI error, even if ITW is disabled", () => {
const globalState = appReducer(undefined, applicationChangeState("active"));

jest
.spyOn(itwLifecycleSelectors, "itwLifecycleIsValidSelector")
.mockImplementation(() => true);

jest
.spyOn(
itwWalletInstanceSelectors,
"itwIsWalletInstanceStatusFailureSelector"
)
.mockImplementation(() => false);

jest
.spyOn(itwSelectors, "isItwEnabledSelector")
.mockImplementation(() => false);

jest
.spyOn(connectivitySelectors, "isConnectedSelector")
.mockImplementation(() => false);

const shouldRenderItwCardsContainer =
shouldRenderItwCardsContainerSelector(globalState);
expect(shouldRenderItwCardsContainer).toBe(true);
});

it("should return false when ITW is disabled and online", () => {
const globalState = appReducer(undefined, applicationChangeState("active"));

jest
.spyOn(itwLifecycleSelectors, "itwLifecycleIsValidSelector")
.mockImplementation(() => true);

jest
.spyOn(
itwWalletInstanceSelectors,
"itwIsWalletInstanceStatusFailureSelector"
)
.mockImplementation(() => false);

jest
.spyOn(itwSelectors, "isItwEnabledSelector")
.mockImplementation(() => false);

jest
.spyOn(connectivitySelectors, "isConnectedSelector")
.mockImplementation(() => true);

const shouldRenderItwCardsContainer =
shouldRenderItwCardsContainerSelector(globalState);
expect(shouldRenderItwCardsContainer).toBe(false);
});

it("should return false when lifecycle is not valid", () => {
const globalState = appReducer(undefined, applicationChangeState("active"));

jest
.spyOn(itwLifecycleSelectors, "itwLifecycleIsValidSelector")
.mockImplementation(() => false);

jest
.spyOn(
itwWalletInstanceSelectors,
"itwIsWalletInstanceStatusFailureSelector"
)
.mockImplementation(() => false);

jest
.spyOn(itwSelectors, "isItwEnabledSelector")
.mockImplementation(() => true);

const shouldRenderItwCardsContainer =
shouldRenderItwCardsContainerSelector(globalState);
expect(shouldRenderItwCardsContainer).toBe(false);
});

it("should return false when there is a WI error", () => {
const globalState = appReducer(undefined, applicationChangeState("active"));

jest
.spyOn(itwLifecycleSelectors, "itwLifecycleIsValidSelector")
.mockImplementation(() => true);

jest
.spyOn(
itwWalletInstanceSelectors,
"itwIsWalletInstanceStatusFailureSelector"
)
.mockImplementation(() => true);

jest
.spyOn(itwSelectors, "isItwEnabledSelector")
.mockImplementation(() => true);

const shouldRenderItwCardsContainer =
shouldRenderItwCardsContainerSelector(globalState);
expect(shouldRenderItwCardsContainer).toBe(false);
});
});
5 changes: 3 additions & 2 deletions ts/features/wallet/store/selectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from "../../types";
import { WalletCardCategoryFilter } from "../../types/index";
import { isItwEnabledSelector } from "../../../itwallet/common/store/selectors/remoteConfig";
import { isConnectedSelector } from "../../../connectivity/store/selectors";

/**
* Returns the list of cards excluding hidden cards
Expand Down Expand Up @@ -169,11 +170,11 @@ export const shouldRenderWalletCategorySelector = createSelector(
/**
* Determines whether the IT Wallet cards section is rendered in the wallet screen.
* The section is rendered if:
* - the IT Wallet feature flag is enabled
* - the IT Wallet feature flag is enabled OR the app is in offline mode
* - the IT Wallet is in a valid lifecycle state
* - the IT Wallet WI does not have an error
*/
export const shouldRenderItwCardsContainerSelector = (state: GlobalState) =>
isItwEnabledSelector(state) &&
(isItwEnabledSelector(state) || !isConnectedSelector(state)) &&
itwLifecycleIsValidSelector(state) &&
!itwIsWalletInstanceStatusFailureSelector(state);
13 changes: 13 additions & 0 deletions ts/sagas/__tests__/initializeApplicationSaga.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import { checkSession } from "../startup/watchCheckSessionSaga";
import { formatRequestedTokenString } from "../../features/zendesk/utils";
import { checkPublicKeyAndBlockIfNeeded } from "../../features/lollipop/navigation";
import { userFromSuccessLoginSelector } from "../../features/login/info/store/selectors";
import { handleWalletCredentialsRehydration } from "../../features/itwallet/credentials/saga/handleWalletCredentialsRehydration";

const aSessionToken = "a_session_token" as SessionToken;
const aSessionInfo = O.some({
Expand Down Expand Up @@ -119,6 +120,8 @@ describe("initializeApplicationSaga", () => {
.next(generateLollipopKeySaga)
.call(checkPublicKeyAndBlockIfNeeded) // is device unsupported?
.next(false) // the device is supported
.fork(handleWalletCredentialsRehydration)
.next()
.select(offlineAccessReasonSelector)
.next()
.select(remoteConfigSelector)
Expand Down Expand Up @@ -176,6 +179,8 @@ describe("initializeApplicationSaga", () => {
.next(generateLollipopKeySaga)
.call(checkPublicKeyAndBlockIfNeeded) // is device unsupported?
.next(false) // the device is supported
.fork(handleWalletCredentialsRehydration)
.next()
.select(offlineAccessReasonSelector)
.next()
.select(remoteConfigSelector)
Expand Down Expand Up @@ -227,6 +232,8 @@ describe("initializeApplicationSaga", () => {
.next(generateLollipopKeySaga)
.call(checkPublicKeyAndBlockIfNeeded) // is device unsupported?
.next(false) // the device is supported
.fork(handleWalletCredentialsRehydration)
.next()
.select(offlineAccessReasonSelector)
.next()
.select(remoteConfigSelector)
Expand Down Expand Up @@ -283,6 +290,8 @@ describe("initializeApplicationSaga", () => {
.next(generateLollipopKeySaga)
.call(checkPublicKeyAndBlockIfNeeded) // is device unsupported?
.next(false) // the device is supported
.fork(handleWalletCredentialsRehydration)
.next()
.select(offlineAccessReasonSelector)
.next()
.select(remoteConfigSelector)
Expand Down Expand Up @@ -352,6 +361,8 @@ describe("initializeApplicationSaga", () => {
.next(generateLollipopKeySaga)
.call(checkPublicKeyAndBlockIfNeeded) // is device unsupported?
.next(false) // the device is supported
.fork(handleWalletCredentialsRehydration)
.next()
.select(offlineAccessReasonSelector)
.next()
.select(remoteConfigSelector)
Expand Down Expand Up @@ -408,6 +419,8 @@ describe("initializeApplicationSaga", () => {
.next(generateLollipopKeySaga)
.call(checkPublicKeyAndBlockIfNeeded) // is device unsupported?
.next(false) // the device is supported
.fork(handleWalletCredentialsRehydration)
.next()
.select(offlineAccessReasonSelector)
.next()
.select(remoteConfigSelector)
Expand Down
6 changes: 5 additions & 1 deletion ts/sagas/startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
offlineAccessReasonSelector
} from "../features/ingress/store/selectors";
import { watchItwSaga } from "../features/itwallet/common/saga";
import { handleWalletCredentialsRehydration } from "../features/itwallet/credentials/saga/handleWalletCredentialsRehydration";
import { userFromSuccessLoginSelector } from "../features/login/info/store/selectors";
import { checkPublicKeyAndBlockIfNeeded } from "../features/lollipop/navigation";
import {
Expand Down Expand Up @@ -228,6 +229,9 @@ export function* initializeApplicationSaga(
}
// #LOLLIPOP_CHECK_BLOCK1_END

// Rehydrate wallet with ITW credentials
yield* fork(handleWalletCredentialsRehydration);

// `isConnectedSelector` was **discarded** as it might be unclear.
// `isStartupLoaded` was **not used** because it required dispatching the action
// that initializes the new navigator before user authentication,
Expand Down Expand Up @@ -528,7 +532,7 @@ export function* initializeApplicationSaga(
yield* call(checkAcknowledgedEmailSaga, userProfile);
}

userProfile = (yield* call(checkEmailSaga)) ?? userProfile;
userProfile = yield* call(checkEmailSaga) ?? userProfile;

Check failure on line 535 in ts/sagas/startup.ts

View workflow job for this annotation

GitHub Actions / static-checks

Type '({ is_inbox_enabled: boolean; is_email_enabled: boolean; is_webhook_enabled: boolean; family_name: string; fiscal_code: string & IPatternStringTag<"^[A-Z]{6}[0-9LMNPQRSTUV]{2}[ABCDEHLMPRST][0-9LMNPQRSTUV]{2}[A-Z][0-9LMNPQRSTUV]{3}[A-Z]$">; has_profile: boolean; name: string; service_preferences_settings: { ...; } & ...' is not assignable to type '{ is_inbox_enabled: boolean; is_email_enabled: boolean; is_webhook_enabled: boolean; family_name: string; fiscal_code: string & IPatternStringTag<"^[A-Z]{6}[0-9LMNPQRSTUV]{2}[ABCDEHLMPRST][0-9LMNPQRSTUV]{2}[A-Z][0-9LMNPQRSTUV]{3}[A-Z]$">; has_profile: boolean; name: string; service_preferences_settings: { ...; } & {...'.

// Check for both profile notifications permissions (anonymous
// content && reminder) and system notifications permissions.
Expand Down

0 comments on commit 2d57f56

Please sign in to comment.