Skip to content

Commit

Permalink
chore(IT Wallet): [SIW-1702] Add automatic subscription to the ITW tr…
Browse files Browse the repository at this point in the history
…ial system (#6228)

> [!WARNING] 
> Can be tested with `io-dev-api-server` by pointing to [this
PR.](pagopa/io-dev-api-server#414)

## Short description
This PR adds an automatic subscription to the ITW trial system when
opening the app.

## List of changes proposed in this pull request
- Add the `handleTrialSystemSubscription` function to check the status
of the trial system. If a citizen is unsubscribed, the function should
automatically subscribe them to the trial system;
- Mock the `CONFIG` object from `react-native-config`; 
- Add tests for `handleTrialSystemSubscription`.

## How to test
This PR can be tested with the `io-dev-api-server` by checking the
network traffic of the app:
- Start `io-dev-api-server` with the default config which is
`SUBSCRIBED`;
- Start the app with the `.env.local` config; 
- Login and check for the network traffic. This can be checked either
with ProxyMan or via the logs of `io-dev-api-server`;
- Do the same test by rotating the trial states in the configuration of
`io-dev-api-server`, either by changing it directly in `config.ts` or
with a custom `config.json` file. The expected behavior is:
```
Any other state -> Only a `GET` to `trialId/subscriptions` should be performed
UNSUBSCRIBED -> Both a `GET` and a `POST` to `trialId/subscriptions` should be performed
```

---------

Co-authored-by: Mario Perrotta <[email protected]>
Co-authored-by: Andrea <[email protected]>
  • Loading branch information
3 people authored Oct 3, 2024
1 parent 0b6483f commit ca8bc32
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 16 deletions.
3 changes: 0 additions & 3 deletions locales/en/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3413,9 +3413,6 @@ features:
title: Certificato di autenticità
usageDescription: Quando ti viene richiesto, mostra il QR Code per attestare l'autenticità del documento.
certifiedLabel: Questo documento è certificato dall'ente emittente.
trialSystem:
toast:
subscribed: Done! You will receive a message when the trial period will begin.
support:
ticketList:
noTicket:
Expand Down
3 changes: 0 additions & 3 deletions locales/it/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3413,9 +3413,6 @@ features:
title: Certificato di autenticità
usageDescription: Quando ti viene richiesto, mostra il QR Code per attestare l'autenticità del documento.
certifiedLabel: Questo documento è certificato dall'ente emittente.
trialSystem:
toast:
subscribed: Fatto! Riceverai un messaggio all’avvio della sperimentazione
support:
ticketList:
noTicket:
Expand Down
3 changes: 2 additions & 1 deletion ts/__mocks__/react-native-config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export default {
UA_DONATIONS_ENABLED: "YES",
PREMIUM_MESSAGES_OPT_IN_ENABLED: "YES",
SCAN_ADDITIONAL_BARCODES_ENABLED: "YES"
SCAN_ADDITIONAL_BARCODES_ENABLED: "YES",
ITW_TRIAL_ID: "baz"
};
149 changes: 149 additions & 0 deletions ts/features/itwallet/common/saga/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { expectSaga } from "redux-saga-test-plan";
import * as pot from "@pagopa/ts-commons/lib/pot";
import { DeepPartial } from "redux";
import * as matchers from "redux-saga-test-plan/matchers";
import { handleTrialSystemSubscription } from "../index";
import { GlobalState } from "../../../../../store/reducers/types";
import {
trialSystemActivationStatus,
trialSystemActivationStatusUpsert
} from "../../../../trialSystem/store/actions";
import { SubscriptionStateEnum } from "../../../../../../definitions/trial_system/SubscriptionState";
import { trialStatusSelector } from "../../../../trialSystem/store/reducers";
import { TrialId } from "../../../../../../definitions/trial_system/TrialId";

describe("handleTrialSystemSubscription", () => {
it("should handle trial system subscription correctly", async () => {
const trialId = "baz" as TrialId;
const state = SubscriptionStateEnum.UNSUBSCRIBED;
const store: DeepPartial<GlobalState> = {
trialSystem: {
[trialId]: pot.some(state)
}
};
return expectSaga(handleTrialSystemSubscription)
.withState(store)
.put(trialSystemActivationStatus.request(trialId))
.dispatch(
trialSystemActivationStatus.success({
trialId,
state,
createdAt: new Date()
})
)
.take([
trialSystemActivationStatus.success,
trialSystemActivationStatus.failure
])
.provide([[matchers.select(trialStatusSelector), state]])
.put(trialSystemActivationStatusUpsert.request(trialId))
.run();
});

it("shouldn't do anything if the user is already subscribed", async () => {
const trialId = "baz" as TrialId;
const state = SubscriptionStateEnum.SUBSCRIBED;
const store: DeepPartial<GlobalState> = {
trialSystem: {
[trialId]: pot.some(state)
}
};
return expectSaga(handleTrialSystemSubscription)
.withState(store)
.put(trialSystemActivationStatus.request(trialId))
.dispatch(
trialSystemActivationStatus.success({
trialId,
state,
createdAt: new Date()
})
)
.take([
trialSystemActivationStatus.success,
trialSystemActivationStatus.failure
])
.provide([[matchers.select(trialStatusSelector), state]])
.not.put(trialSystemActivationStatusUpsert.request(trialId))
.run();
});

it("shouldn't do anything if the user is active", async () => {
const trialId = "baz" as TrialId;
const state = SubscriptionStateEnum.ACTIVE;
const store: DeepPartial<GlobalState> = {
trialSystem: {
[trialId]: pot.some(state)
}
};
return expectSaga(handleTrialSystemSubscription)
.withState(store)
.put(trialSystemActivationStatus.request(trialId))
.dispatch(
trialSystemActivationStatus.success({
trialId,
state,
createdAt: new Date()
})
)
.take([
trialSystemActivationStatus.success,
trialSystemActivationStatus.failure
])
.provide([[matchers.select(trialStatusSelector), state]])
.not.put(trialSystemActivationStatusUpsert.request(trialId))
.run();
});

it("shouldn't do anything if the user is disabled", async () => {
const trialId = "baz" as TrialId;
const state = SubscriptionStateEnum.DISABLED;
const store: DeepPartial<GlobalState> = {
trialSystem: {
[trialId]: pot.some(state)
}
};
return expectSaga(handleTrialSystemSubscription)
.withState(store)
.put(trialSystemActivationStatus.request(trialId))
.dispatch(
trialSystemActivationStatus.success({
trialId,
state,
createdAt: new Date()
})
)
.take([
trialSystemActivationStatus.success,
trialSystemActivationStatus.failure
])
.provide([[matchers.select(trialStatusSelector), state]])
.not.put(trialSystemActivationStatusUpsert.request(trialId))
.run();
});

it("shouldn't do anything if an error occurs", async () => {
const trialId = "baz" as TrialId;
const state = SubscriptionStateEnum.UNSUBSCRIBED;
const store: DeepPartial<GlobalState> = {
trialSystem: {
[trialId]: pot.some(state)
}
};
return expectSaga(handleTrialSystemSubscription)
.withState(store)
.put(trialSystemActivationStatus.request(trialId))
.dispatch(
trialSystemActivationStatus.failure({
trialId,
error: new Error("foo")
})
)
.take([
trialSystemActivationStatus.success,
trialSystemActivationStatus.failure
])
.provide([[matchers.select(trialStatusSelector), state]])
.not.put(trialSystemActivationStatusUpsert.request(trialId))
.run();
});
});
32 changes: 28 additions & 4 deletions ts/features/itwallet/common/saga/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { SagaIterator } from "redux-saga";
import { fork, put, call } from "typed-redux-saga/macro";
import { trialSystemActivationStatus } from "../../../trialSystem/store/actions";
import { fork, put, call, take, select } from "typed-redux-saga/macro";
import { isActionOf } from "typesafe-actions";
import {
trialSystemActivationStatus,
trialSystemActivationStatusUpsert
} from "../../../trialSystem/store/actions";
import { watchItwIdentificationSaga } from "../../identification/saga";
import { checkWalletInstanceStateSaga } from "../../lifecycle/saga/checkWalletInstanceStateSaga";
import { handleWalletCredentialsRehydration } from "../../credentials/saga/handleWalletCredentialsRehydration";
Expand All @@ -9,6 +13,8 @@ import { itwCieIsSupported } from "../../identification/store/actions";
import { watchItwCredentialsSaga } from "../../credentials/saga";
import { watchItwLifecycleSaga } from "../../lifecycle/saga";
import { checkCredentialsStatusAttestation } from "../../credentials/saga/checkCredentialsStatusAttestation";
import { trialStatusSelector } from "../../../trialSystem/store/reducers";
import { SubscriptionStateEnum } from "../../../../../definitions/trial_system/SubscriptionState";

function* checkWalletInstanceAndCredentialsValiditySaga() {
// Status attestations of credentials are checked only in case of a valid wallet instance.
Expand All @@ -17,15 +23,33 @@ function* checkWalletInstanceAndCredentialsValiditySaga() {
yield* call(checkCredentialsStatusAttestation);
}

/**
* Handle the trial system subscription which currently fetches the trial status
* and if the user is unsubscribed it automatically subscribes the user to the trial.
*/
export function* handleTrialSystemSubscription() {
// IT Wallet trial status refresh
yield* put(trialSystemActivationStatus.request(itwTrialId));
const outputAction = yield* take([
trialSystemActivationStatus.success,
trialSystemActivationStatus.failure
]);
if (isActionOf(trialSystemActivationStatus.success, outputAction)) {
const status = yield* select(trialStatusSelector(itwTrialId));
if (status && status === SubscriptionStateEnum.UNSUBSCRIBED) {
yield* put(trialSystemActivationStatusUpsert.request(itwTrialId));
}
}
}

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

// TODO: [SIW-1404] remove this CIE check and move the logic to xstate
yield* put(itwCieIsSupported.request());
// IT Wallet trial status refresh
yield* put(trialSystemActivationStatus.request(itwTrialId));
}
5 changes: 0 additions & 5 deletions ts/features/trialSystem/store/sagas/watchTrialSystemSaga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { SagaIterator } from "redux-saga";
import { call, put, takeLatest } from "typed-redux-saga/macro";
import { ActionType } from "typesafe-actions";
import { readableReport } from "@pagopa/ts-commons/lib/reporters";
import { IOToast } from "@pagopa/io-app-design-system";
import { SessionToken } from "../../../../types/SessionToken";
import { TrialSystemClient, createTrialSystemClient } from "../../api/client";
import { apiUrlPrefix } from "../../../../config";
Expand All @@ -13,7 +12,6 @@ import {
trialSystemActivationStatusUpsert
} from "../actions";
import { getError } from "../../../../utils/errors";
import I18n from "../../../../i18n";

function* handleTrialSystemActivationStatusUpsert(
upsertTrialSystemActivationStatus: TrialSystemClient["createSubscription"],
Expand All @@ -33,15 +31,13 @@ function* handleTrialSystemActivationStatusUpsert(
);
} else if (result.right.status === 201) {
yield* put(trialSystemActivationStatusUpsert.success(result.right.value));
IOToast.success(I18n.t("features.trialSystem.toast.subscribed"));
} else {
yield* put(
trialSystemActivationStatusUpsert.failure({
trialId: action.payload,
error: new Error(`response status ${result.right.status}`)
})
);
IOToast.error(I18n.t("global.genericError"));
}
} catch (e) {
yield* put(
Expand All @@ -50,7 +46,6 @@ function* handleTrialSystemActivationStatusUpsert(
error: getError(e)
})
);
IOToast.error(I18n.t("global.genericError"));
}
}

Expand Down

0 comments on commit ca8bc32

Please sign in to comment.