Skip to content

Commit

Permalink
chore: [IOPID-1372,IOPID-1373] Add remote ToS management (#5446)
Browse files Browse the repository at this point in the history
### ⚠️ This PR Depends on
pagopa/io-services-metadata#764 ⚠️ ###

## Short description
This PR adds the remote ToS management feature.

<details open><summary>Details</summary>
<p>

| Custom Url | Default Url |
| - | - |
| <video
src="https://github.com/pagopa/io-app/assets/16268789/fe048576-f519-4a78-a336-7cb97f0e20d2"
/> | <video
src="https://github.com/pagopa/io-app/assets/16268789/45b695ba-ed02-4d35-92ae-2a37353e675b"
/>

</p>

</details> 

## List of changes proposed in this pull request
- Bump io-servives-metadata tag from 1.0.29 to 1.0.30
- Add the remote ToS management

## How to test
Run the app against [this dev server
branch](https://github.com/pagopa/io-dev-api-server/tree/IOPID-1374-remote-tos)
and try to change [`ToS
version`](https://github.com/pagopa/io-dev-api-server/blob/4df1c0c1f17f1e633a1fb6b77cd9df4c7142beff/src/payloads/backend.ts#L125)
(with a number greater than the one in the profile) and [`ToS
url`](https://github.com/pagopa/io-dev-api-server/blob/4df1c0c1f17f1e633a1fb6b77cd9df4c7142beff/src/payloads/backend.ts#L126)
with a custom one. Then restore the ToS url to the expected one
`https://io.italia.it/app-content/tos_privacy.html` and test that it's
shown too.

---------

Co-authored-by: Alice Di Rico <[email protected]>
  • Loading branch information
shadowsheep1 and Ladirico authored Jan 31, 2024
1 parent 8cd556b commit 585f6d4
Show file tree
Hide file tree
Showing 12 changed files with 137 additions and 38 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "2.52.0-rc.0",
"io_backend_api": "https://raw.githubusercontent.com/pagopa/io-backend/v13.25.1-RELEASE/api_backend.yaml",
"io_public_api": "https://raw.githubusercontent.com/pagopa/io-backend/v13.25.1-RELEASE/api_public.yaml",
"io_content_specs": "https://raw.githubusercontent.com/pagopa/io-services-metadata/1.0.29/definitions.yml",
"io_content_specs": "https://raw.githubusercontent.com/pagopa/io-services-metadata/1.0.30/definitions.yml",
"io_cgn_specs": "https://raw.githubusercontent.com/pagopa/io-backend/v13.25.1-RELEASE/api_cgn.yaml",
"io_cgn_merchants_specs": "https://raw.githubusercontent.com/pagopa/io-backend/v13.29.2-RELEASE/api_cgn_operator_search.yaml",
"api_fci": "https://raw.githubusercontent.com/pagopa/io-backend/v13.25.1-RELEASE/api_io_sign.yaml",
Expand All @@ -14,9 +14,9 @@
"api_sicilia_vola": "assets/SiciliaVola.yml",
"api_cdc": "assets/CdcSwagger.yml",
"pagopa_api": "assets/paymentManager/spec.json",
"pagopa_api_walletv2": "https://raw.githubusercontent.com/pagopa/io-services-metadata/1.0.29/bonus/specs/bpd/pm/walletv2.json",
"pagopa_cobadge_configuration": "https://raw.githubusercontent.com/pagopa/io-services-metadata/1.0.29/pagopa/cobadge/abi_definitions.yml",
"pagopa_privative_configuration": "https://raw.githubusercontent.com/pagopa/io-services-metadata/1.0.29/pagopa/privative/definitions.yml",
"pagopa_api_walletv2": "https://raw.githubusercontent.com/pagopa/io-services-metadata/1.0.30/bonus/specs/bpd/pm/walletv2.json",
"pagopa_cobadge_configuration": "https://raw.githubusercontent.com/pagopa/io-services-metadata/1.0.30/pagopa/cobadge/abi_definitions.yml",
"pagopa_privative_configuration": "https://raw.githubusercontent.com/pagopa/io-services-metadata/1.0.30/pagopa/privative/definitions.yml",
"idpay_api": "https://raw.githubusercontent.com/pagopa/cstar-infrastructure/v6.9.1/src/domains/idpay-app/api/idpay_appio_full/openapi.appio.full.yml",
"lollipop_api": "https://raw.githubusercontent.com/pagopa/io-backend/v13.25.1-RELEASE/api_lollipop_first_consumer.yaml",
"fast_login_api": "https://raw.githubusercontent.com/pagopa/io-backend/v13.25.1-RELEASE/openapi/generated/api_fast_login.yaml",
Expand Down
4 changes: 0 additions & 4 deletions ts/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Main config file. Mostly read the configuration from .env files

import { CommaSeparatedListOf } from "@pagopa/ts-commons/lib/comma-separated-list";
import { NonNegativeNumber } from "@pagopa/ts-commons/lib/numbers";
import { NonEmptyString } from "@pagopa/ts-commons/lib/strings";
import { Millisecond, Second } from "@pagopa/ts-commons/lib/units";
import * as E from "fp-ts/lib/Either";
Expand Down Expand Up @@ -109,9 +108,6 @@ export const remindersOptInEnabled = Config.REMINDERS_OPT_IN_ENABLED === "YES";

export const isNewCduFlow = Config.CDU_NEW_FLOW === "YES";

// version of ToS
export const tosVersion: NonNegativeNumber = 4.8 as NonNegativeNumber;

export const fetchTimeout = pipe(
parseInt(Config.FETCH_TIMEOUT_MS, 10),
t.Integer.decode,
Expand Down
7 changes: 7 additions & 0 deletions ts/features/tos/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { NonNegativeNumber } from "@pagopa/ts-commons/lib/numbers";
import { TosConfig } from "../../../definitions/content/TosConfig";

export const getTosVersion = (tosData: TosConfig): NonNegativeNumber =>
tosData.tos_version as NonNegativeNumber;

export const getTosUrl = (tosData: TosConfig): string => tosData.tos_url;
65 changes: 65 additions & 0 deletions ts/features/tos/store/selectors/__tests__/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import * as O from "fp-ts/lib/Option";
import { NonNegativeNumber } from "@pagopa/ts-commons/lib/numbers";
import { BackendStatus } from "../../../../../../definitions/content/BackendStatus";
import { baseRawBackendStatus } from "../../../../../store/reducers/__mock__/backendStatus";
import { GlobalState } from "../../../../../store/reducers/types";
import { TosConfig } from "../../../../../../definitions/content/TosConfig";
import { getTosUrl, getTosVersion } from "../../..";

const TOS_CONFIG: TosConfig = {
tos_url: "https://www.example.com",
tos_version: 3.2 as NonNegativeNumber
};

const status: BackendStatus = {
...baseRawBackendStatus
};

const customStore = {
backendStatus: {
status: O.some({
...status,
config: {
...status.config,
tos: TOS_CONFIG
}
})
}
} as unknown as GlobalState;

function runTest(store: GlobalState, test: (tosConfig: TosConfig) => void) {
const actualStatus = store.backendStatus.status;
expect(O.isSome(actualStatus)).toBe(true);
if (O.isSome(actualStatus)) {
const tosConfig = actualStatus.value.config.tos;
expect(tosConfig).not.toBeUndefined();
test(tosConfig);
} else {
fail("unexpected none");
}
}

describe("Tos config", () => {
it("should return a value", () => {
runTest(customStore, tosConfig => {
expect(getTosVersion(tosConfig)).not.toBeUndefined();
expect(getTosUrl(tosConfig)).not.toBeNull();
expect(getTosVersion(tosConfig)).not.toBeNaN();
});
});
it("should be right", () => {
runTest(customStore, tosConfig => {
expect(getTosVersion(tosConfig)).toBe(3.2);
expect(getTosUrl(tosConfig)).toBe("https://www.example.com");
});
});
it("should not be wrong", () => {
runTest(customStore, tosConfig => {
expect(getTosVersion(tosConfig)).not.toBe(3.0);
expect(getTosVersion(tosConfig)).not.toBe(3);
expect(getTosUrl(tosConfig)).not.toBe("http://www.example.com");
expect(getTosVersion(tosConfig)).not.toBe(3.8);
expect(getTosUrl(tosConfig)).not.toBe("http://www.example.it");
});
});
});
24 changes: 24 additions & 0 deletions ts/features/tos/store/selectors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createSelector } from "reselect";
import { pipe } from "fp-ts/lib/function";
import * as O from "fp-ts/lib/Option";
import { TosConfig } from "../../../../../definitions/content/TosConfig";
import { privacyUrl } from "../../../../config";
import { backendStatusSelector } from "../../../../store/reducers/backendStatus";

const DEFAULT_TOS_CONFIG: TosConfig = {
tos_url: privacyUrl,
tos_version: 4.8
};

export const tosConfigSelector = createSelector(
backendStatusSelector,
backendStatus =>
pipe(
backendStatus,
O.chainNullableK(bs => bs.config.tos),
O.fold(
() => DEFAULT_TOS_CONFIG,
v => v
)
)
);
4 changes: 3 additions & 1 deletion ts/sagas/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { ServicesPreferencesModeEnum } from "../../definitions/backend/ServicesP
import { UpdateProfile412ErrorTypesEnum } from "../../definitions/backend/UpdateProfile412ErrorTypes";
import { UserDataProcessingChoiceEnum } from "../../definitions/backend/UserDataProcessingChoice";
import { BackendClient } from "../api/backend";
import { tosVersion } from "../config";
import { cgnDetails } from "../features/bonus/cgn/store/actions/details";
import { cgnDetailSelector } from "../features/bonus/cgn/store/reducers/details";
import { withRefreshApiCall } from "../features/fastLogin/saga/utils";
Expand Down Expand Up @@ -52,6 +51,7 @@ import {
getLocalePrimaryWithFallback
} from "../utils/locale";
import { readablePrivacyReport } from "../utils/reporters";
import { tosConfigSelector } from "../features/tos/store/selectors";

// A saga to load the Profile.
export function* loadProfile(
Expand Down Expand Up @@ -108,6 +108,8 @@ function* createOrUpdateProfileSaga(
return;
}

const tosVersion = (yield* select(tosConfigSelector)).tos_version;

const currentProfile = profileState.value;

const rawAppVersion = yield* call(getAppVersion);
Expand Down
18 changes: 8 additions & 10 deletions ts/sagas/startup/__tests__/checkAcceptedTosSaga.test.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import { createStore } from "redux";
import { expectSaga } from "redux-saga-test-plan";

import {
NonNegativeInteger,
NonNegativeNumber
} from "@pagopa/ts-commons/lib/numbers";
import { View } from "react-native";
import { tosVersion } from "../../../config";
import { applicationChangeState } from "../../../store/actions/application";
import { select } from "redux-saga/effects";
import { navigateToTosScreen } from "../../../store/actions/navigation";
import { tosAccepted } from "../../../store/actions/onboarding";
import { appReducer } from "../../../store/reducers";
import { isProfileFirstOnBoarding } from "../../../store/reducers/profile";
import { renderScreenWithNavigationStoreContext } from "../../../utils/testWrapper";
import mockedProfile from "../../../__mocks__/initializedProfile";
import { checkAcceptedTosSaga } from "../checkAcceptedTosSaga";
import { ServicesPreferencesModeEnum } from "../../../../definitions/backend/ServicesPreferencesMode";
import { tosConfigSelector } from "../../../features/tos/store/selectors";

const tosVersion = 3.2 as NonNegativeNumber;

describe("checkAcceptedTosSaga", () => {
const firstOnboardingProfile = {
Expand Down Expand Up @@ -50,9 +47,6 @@ describe("checkAcceptedTosSaga", () => {
};

beforeEach(() => {
const globalState = appReducer(undefined, applicationChangeState("active"));
const store = createStore(appReducer, globalState as any);
renderScreenWithNavigationStoreContext(View, "DUMMY", {}, store);
jest.useRealTimers();
});

Expand All @@ -69,6 +63,7 @@ describe("checkAcceptedTosSaga", () => {
describe("when user has already accepted the last version of ToS", () => {
it("should do nothing", () =>
expectSaga(checkAcceptedTosSaga, updatedProfile)
.provide([[select(tosConfigSelector), { tos_version: tosVersion }]])
.not.call(navigateToTosScreen)
.run());
});
Expand All @@ -79,13 +74,15 @@ describe("checkAcceptedTosSaga", () => {
...oldOnboardedProfile,
accepted_tos_version: undefined
})
.provide([[select(tosConfigSelector), { tos_version: tosVersion }]])
.call(navigateToTosScreen)
.run());
});

describe("when user has accepted an old version of ToS", () => {
it("should navigate to the terms of service screen and succeed when ToS get accepted", () =>
expectSaga(checkAcceptedTosSaga, notUpdatedProfile)
.provide([[select(tosConfigSelector), { tos_version: tosVersion }]])
.call(navigateToTosScreen)
.take(tosAccepted)
.run());
Expand All @@ -94,6 +91,7 @@ describe("checkAcceptedTosSaga", () => {
describe("when user has never accepted an ToS because he is accessing the app for the first time", () => {
it("should navigate to the terms of service screen and succeed when ToS get accepted", () =>
expectSaga(checkAcceptedTosSaga, firstOnboardingProfile)
.provide([[select(tosConfigSelector), { tos_version: tosVersion }]])
.call(navigateToTosScreen)
.take(tosAccepted)
.run());
Expand Down
5 changes: 3 additions & 2 deletions ts/sagas/startup/checkAcceptedTosSaga.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { call, put, take } from "typed-redux-saga/macro";
import { call, put, select, take } from "typed-redux-saga/macro";
import { ActionType, getType } from "typesafe-actions";
import { StackActions } from "@react-navigation/native";
import { InitializedProfile } from "../../../definitions/backend/InitializedProfile";
import { tosVersion } from "../../config";
import { navigateToTosScreen } from "../../store/actions/navigation";
import { tosAccepted } from "../../store/actions/onboarding";
import { profileUpsert } from "../../store/actions/profile";
import { isProfileFirstOnBoarding } from "../../store/reducers/profile";
import { ReduxSagaEffect } from "../../types/utils";
import NavigationService from "../../navigation/NavigationService";
import { tosConfigSelector } from "../../features/tos/store/selectors";

export function* checkAcceptedTosSaga(
userProfile: InitializedProfile
Expand All @@ -18,6 +18,7 @@ export function* checkAcceptedTosSaga(
| ActionType<(typeof profileUpsert)["success"]>
| ActionType<(typeof profileUpsert)["failure"]>
> {
const tosVersion = (yield* select(tosConfigSelector)).tos_version;
// The user has to explicitly accept the new version of ToS if:
// - this is the first access
// - the user profile stores the user accepted an old version of ToS
Expand Down
6 changes: 5 additions & 1 deletion ts/screens/onboarding/OnboardingTosScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import BaseScreenComponent, {
ContextualHelpPropsMarkdown
} from "../../components/screens/BaseScreenComponent";
import TosWebviewComponent from "../../components/TosWebviewComponent";
import { privacyUrl, tosVersion } from "../../config";
import I18n from "../../i18n";
import { abortOnboarding, tosAccepted } from "../../store/actions/onboarding";
import { useIODispatch, useIOSelector } from "../../store/hooks";
Expand All @@ -31,6 +30,7 @@ import { trackTosUserExit } from "../authentication/analytics";
import { getFlowType } from "../../utils/analytics";
import { useOnFirstRender } from "../../utils/hooks/useOnFirstRender";
import { trackTosAccepted, trackTosScreen } from "../profile/analytics";
import { tosConfigSelector } from "../../features/tos/store/selectors";

const styles = StyleSheet.create({
titlePadding: {
Expand Down Expand Up @@ -74,6 +74,10 @@ const OnboardingTosScreen = () => {
trackTosScreen(getFlowType(true, isFirstOnBoarding));
});

const tosConfig = useIOSelector(tosConfigSelector);
const tosVersion = tosConfig.tos_version;
const privacyUrl = tosConfig.tos_url;

const hasAcceptedCurrentTos = pot.getOrElse(
pot.map(potProfile, p => p.accepted_tos_version === tosVersion),
false
Expand Down
11 changes: 4 additions & 7 deletions ts/screens/onboarding/__tests__/OnboardingTosScreen.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ const CurrentTestZendeskEnabled = true;
const CurrentTestToSVersion = 2.0;

const zendeskEnabledDefaultValue = config.zendeskEnabled;
const tosVersionOriginalValue = config.tosVersion;

// Restore defineProperty
beforeAll(() => {
Expand All @@ -37,8 +36,6 @@ beforeAll(() => {
Object.defineProperty(config, "zendeskEnabled", {
value: CurrentTestZendeskEnabled
});
// eslint-disable-next-line functional/immutable-data
Object.defineProperty(config, "tosVersion", { value: CurrentTestToSVersion });
});

afterAll(() => {
Expand All @@ -48,10 +45,6 @@ afterAll(() => {
Object.defineProperty(config, "zendeskEnabled", {
value: zendeskEnabledDefaultValue
});
// eslint-disable-next-line functional/immutable-data
Object.defineProperty(config, "tosVersion", {
value: tosVersionOriginalValue
});
});

describe("TosScreen", () => {
Expand Down Expand Up @@ -405,6 +398,10 @@ const commonSetup = ({
},
fims: {
enabled: false
},
tos: {
tos_version: CurrentTestToSVersion,
tos_url: "https://www.example.com"
}
}
})
Expand Down
11 changes: 4 additions & 7 deletions ts/screens/profile/__test__/TosScreen.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const CurrentTestZendeskEnabled = true;
const CurrentTestToSVersion = 2.0;

const zendeskEnabledDefaultValue = config.zendeskEnabled;
const tosVersionOriginalValue = config.tosVersion;

// Restore defineProperty
beforeAll(() => {
Expand All @@ -33,8 +32,6 @@ beforeAll(() => {
Object.defineProperty(config, "zendeskEnabled", {
value: CurrentTestZendeskEnabled
});
// eslint-disable-next-line functional/immutable-data
Object.defineProperty(config, "tosVersion", { value: CurrentTestToSVersion });
});

afterAll(() => {
Expand All @@ -44,10 +41,6 @@ afterAll(() => {
Object.defineProperty(config, "zendeskEnabled", {
value: zendeskEnabledDefaultValue
});
// eslint-disable-next-line functional/immutable-data
Object.defineProperty(config, "tosVersion", {
value: tosVersionOriginalValue
});
});

describe("TosScreen", () => {
Expand Down Expand Up @@ -211,6 +204,10 @@ const commonSetup = () => {
},
fims: {
enabled: false
},
tos: {
tos_version: 3.2,
tos_url: "https://www.example.com"
}
}
})
Expand Down
12 changes: 10 additions & 2 deletions ts/store/reducers/__mock__/backendStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,11 @@ export const baseRawBackendStatus: BackendStatus = {
frontend_url: "",
optInServiceId: ""
},
payments: {}
payments: {},
tos: {
tos_url: "https://www.example.com",
tos_version: 3.2
}
}
};

Expand Down Expand Up @@ -385,7 +389,11 @@ export const baseBackendConfig: Config = {
frontend_url: "",
optInServiceId: ""
},
payments: {}
payments: {},
tos: {
tos_url: "https://www.example.com",
tos_version: 3.2
}
};

export const withBpdRankingConfig = (
Expand Down

0 comments on commit 585f6d4

Please sign in to comment.