From febfc0194871a0759c685b9d8c6a50b1c731e8ca Mon Sep 17 00:00:00 2001 From: Alessandro Izzo Date: Mon, 15 Jan 2024 09:58:15 +0100 Subject: [PATCH] chore: [PE-510] Integration of LV for CGN flows (#5363) ## Short description This PR integrates LV to all CGN API requests; ## List of changes proposed in this pull request - Added the `withRefreshApiCall` handler to all CGN API requests into relative sagas - Upgraded the CGN merchants definitions ## How to test - Enable the login veloce feature on the dev-server and decrease the session time. - Open any CGN screen while the token is expired - You should be able to see the session expired screen --------- Co-authored-by: Fabio Bombardi <16268789+shadowsheep1@users.noreply.github.com> --- package.json | 2 +- .../networking/__test__/getEycaStatus.test.ts | 32 +++++++++++--- .../bonus/cgn/saga/networking/bucket/index.ts | 10 +++-- .../categories/cgnCategoriesSaga.ts | 19 +++++++-- .../details/getCgnInformationSaga.ts | 13 ++++-- .../eyca/activation/getEycaActivationSaga.ts | 19 ++++++--- .../networking/eyca/details/getEycaStatus.ts | 13 ++++-- .../networking/merchants/cgnMerchantsSaga.ts | 42 +++++++++++++++---- .../bonus/cgn/saga/networking/otp/index.ts | 13 ++++-- .../cgn/saga/networking/unsubscribe/index.ts | 14 +++++-- 10 files changed, 135 insertions(+), 42 deletions(-) diff --git a/package.json b/package.json index 7f7f2e1c3fb..60801b2b044 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "io_content_specs": "https://raw.githubusercontent.com/pagopa/io-services-metadata/1.0.29/definitions.yml", "io_bonus_vacanze_specs": "https://raw.githubusercontent.com/pagopa/io-backend/v13.25.1-RELEASE/api_bonus.yaml", "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.25.1-RELEASE/api_cgn_operator_search.yaml", + "io_cgn_merchants_specs": "https://raw.githubusercontent.com/pagopa/io-backend/v13.29.2-RELEASE/api_cgn_operator_search.yaml", "io_bpd_citizen": "https://raw.githubusercontent.com/pagopa/io-services-metadata/1.0.29/bonus/specs/bpd/citizen.json", "io_bpd_citizen_v2": "https://raw.githubusercontent.com/pagopa/io-services-metadata/1.0.29/bonus/specs/bpd/citizen_v2.json", "io_bpd_payment": "https://raw.githubusercontent.com/pagopa/io-services-metadata/1.0.29/bonus/specs/bpd/payment.json", diff --git a/ts/features/bonus/cgn/saga/networking/__test__/getEycaStatus.test.ts b/ts/features/bonus/cgn/saga/networking/__test__/getEycaStatus.test.ts index ef6adc079ad..129f1772861 100644 --- a/ts/features/bonus/cgn/saga/networking/__test__/getEycaStatus.test.ts +++ b/ts/features/bonus/cgn/saga/networking/__test__/getEycaStatus.test.ts @@ -54,7 +54,7 @@ describe("handleGetEycaStatus", () => { E.right({ status: 200, value: eycaCard }) ); it("With 200 should be FOUND and have an eyca card", () => - expectSaga(handleGetEycaStatus, getEycaStatus) + expectSaga(handleGetEycaStatus, getEycaStatus, cgnEycaStatus.request()) .withReducer(appReducer) .put(cgnEycaStatus.success({ status: "FOUND", card: eycaCard })) .run() @@ -70,7 +70,11 @@ describe("handleGetEycaStatus", () => { it("With 404 should be NOT_FOUND", () => { const getEycaStatus = jest.fn(); getEycaStatus.mockImplementation(() => E.right({ status: 404 })); - return expectSaga(handleGetEycaStatus, getEycaStatus) + return expectSaga( + handleGetEycaStatus, + getEycaStatus, + cgnEycaStatus.request() + ) .withReducer(appReducer) .put(cgnEycaStatus.success({ status: "NOT_FOUND" })) .run() @@ -86,7 +90,11 @@ describe("handleGetEycaStatus", () => { it("With 403 should be INELIGIBLE", () => { const getEycaStatus = jest.fn(); getEycaStatus.mockImplementation(() => E.right({ status: 403 })); - return expectSaga(handleGetEycaStatus, getEycaStatus) + return expectSaga( + handleGetEycaStatus, + getEycaStatus, + cgnEycaStatus.request() + ) .withReducer(appReducer) .put(cgnEycaStatus.success({ status: "INELIGIBLE" })) .run() @@ -102,7 +110,11 @@ describe("handleGetEycaStatus", () => { it(`With 409 status should be ERROR`, () => { const getEycaStatus = jest.fn(); getEycaStatus.mockImplementation(() => E.right({ status: 409 })); - return expectSaga(handleGetEycaStatus, getEycaStatus) + return expectSaga( + handleGetEycaStatus, + getEycaStatus, + cgnEycaStatus.request() + ) .withReducer(appReducer) .put(cgnEycaStatus.success({ status: "ERROR" })) .run() @@ -120,7 +132,11 @@ describe("handleGetEycaStatus", () => { const error = getGenericError(new Error(`response status ${status}`)); const getEycaStatus = jest.fn(); getEycaStatus.mockImplementation(() => E.right({ status })); - return expectSaga(handleGetEycaStatus, getEycaStatus) + return expectSaga( + handleGetEycaStatus, + getEycaStatus, + cgnEycaStatus.request() + ) .withReducer(appReducer) .put(cgnEycaStatus.failure(error)) .run() @@ -144,7 +160,11 @@ describe("handleGetEycaStatus", () => { ) ); getEycaStatus.mockImplementation(() => error); - return expectSaga(handleGetEycaStatus, getEycaStatus) + return expectSaga( + handleGetEycaStatus, + getEycaStatus, + cgnEycaStatus.request() + ) .withReducer(appReducer) .put(cgnEycaStatus.failure(genericError)) .run() diff --git a/ts/features/bonus/cgn/saga/networking/bucket/index.ts b/ts/features/bonus/cgn/saga/networking/bucket/index.ts index 7207f0fc465..507a81d20ed 100644 --- a/ts/features/bonus/cgn/saga/networking/bucket/index.ts +++ b/ts/features/bonus/cgn/saga/networking/bucket/index.ts @@ -5,6 +5,7 @@ import { getNetworkError } from "../../../../../../utils/errors"; import { readablePrivacyReport } from "../../../../../../utils/reporters"; import { BackendCgnMerchants } from "../../../api/backendCgnMerchants"; import { cgnCodeFromBucket } from "../../../store/actions/bucket"; +import { withRefreshApiCall } from "../../../../../fastLogin/saga/utils"; // handle the request for CGN bucket consumption export function* cgnBucketConsuption( @@ -14,11 +15,14 @@ export function* cgnBucketConsuption( cgnCodeFromBucketRequest: ReturnType<(typeof cgnCodeFromBucket)["request"]> ) { try { - const discountBucketCodeResult: SagaCallReturnType< - typeof getDiscountBucketCode - > = yield* call(getDiscountBucketCode, { + const discountBacketRequest = getDiscountBucketCode({ discountId: cgnCodeFromBucketRequest.payload }); + const discountBucketCodeResult = (yield* call( + withRefreshApiCall, + discountBacketRequest, + cgnCodeFromBucketRequest + )) as unknown as SagaCallReturnType; if (E.isRight(discountBucketCodeResult)) { if (discountBucketCodeResult.right.status === 200) { yield* put( diff --git a/ts/features/bonus/cgn/saga/networking/categories/cgnCategoriesSaga.ts b/ts/features/bonus/cgn/saga/networking/categories/cgnCategoriesSaga.ts index 3ab9b4d7952..3238a69fc4c 100644 --- a/ts/features/bonus/cgn/saga/networking/categories/cgnCategoriesSaga.ts +++ b/ts/features/bonus/cgn/saga/networking/categories/cgnCategoriesSaga.ts @@ -1,5 +1,6 @@ import { readableReport } from "@pagopa/ts-commons/lib/reporters"; import * as E from "fp-ts/lib/Either"; +import { ActionType } from "typesafe-actions"; import { call, put } from "typed-redux-saga/macro"; import { PublishedProductCategories } from "../../../../../../../definitions/cgn/merchants/PublishedProductCategories"; import { PublishedProductCategoriesWithNewDiscountsCount } from "../../../../../../../definitions/cgn/merchants/PublishedProductCategoriesWithNewDiscountsCount"; @@ -10,6 +11,7 @@ import { } from "../../../../../../utils/errors"; import { BackendCgnMerchants } from "../../../api/backendCgnMerchants"; import { cgnCategories } from "../../../store/actions/categories"; +import { withRefreshApiCall } from "../../../../../fastLogin/saga/utils"; const checkIsCategoriesWithCount = ( cl: @@ -22,12 +24,18 @@ const checkIsCategoriesWithCount = ( export function* cgnCategoriesSaga( getPublishedCategories: ReturnType< typeof BackendCgnMerchants - >["getPublishedCategories"] + >["getPublishedCategories"], + action: ActionType<(typeof cgnCategories)["request"]> ) { try { - const publishedCategoriesResult: SagaCallReturnType< - typeof getPublishedCategories - > = yield* call(getPublishedCategories, { count_new_discounts: true }); + const publishedCategoriesRequest = getPublishedCategories({ + count_new_discounts: true + }); + const publishedCategoriesResult = (yield* call( + withRefreshApiCall, + publishedCategoriesRequest, + action + )) as unknown as SagaCallReturnType; if (E.isLeft(publishedCategoriesResult)) { yield* put( cgnCategories.failure( @@ -48,6 +56,9 @@ export function* cgnCategoriesSaga( throw new Error( `Expected a ProductCategoryWithNewDiscountsCount but received PublishedProductCategories` ); + } + + if (publishedCategoriesResult.right.status === 401) { return; } diff --git a/ts/features/bonus/cgn/saga/networking/details/getCgnInformationSaga.ts b/ts/features/bonus/cgn/saga/networking/details/getCgnInformationSaga.ts index 9c5d1b14c96..b86f7df6cd8 100644 --- a/ts/features/bonus/cgn/saga/networking/details/getCgnInformationSaga.ts +++ b/ts/features/bonus/cgn/saga/networking/details/getCgnInformationSaga.ts @@ -1,3 +1,4 @@ +import { ActionType } from "typesafe-actions"; import { readableReport } from "@pagopa/ts-commons/lib/reporters"; import * as E from "fp-ts/lib/Either"; import { call, put } from "typed-redux-saga/macro"; @@ -5,13 +6,19 @@ import { SagaCallReturnType } from "../../../../../../types/utils"; import { getNetworkError } from "../../../../../../utils/errors"; import { BackendCGN } from "../../../api/backendCgn"; import { cgnDetails } from "../../../store/actions/details"; +import { withRefreshApiCall } from "../../../../../fastLogin/saga/utils"; export function* cgnGetInformationSaga( - getCgnStatus: ReturnType["getCgnStatus"] + getCgnStatus: ReturnType["getCgnStatus"], + action: ActionType<(typeof cgnDetails)["request"]> ) { try { - const cgnInformationResult: SagaCallReturnType = - yield* call(getCgnStatus, {}); + const cgnInformationRequest = getCgnStatus({}); + const cgnInformationResult = (yield* call( + withRefreshApiCall, + cgnInformationRequest, + action + )) as unknown as SagaCallReturnType; if (E.isLeft(cgnInformationResult)) { yield* put( cgnDetails.failure({ diff --git a/ts/features/bonus/cgn/saga/networking/eyca/activation/getEycaActivationSaga.ts b/ts/features/bonus/cgn/saga/networking/eyca/activation/getEycaActivationSaga.ts index 161fc12349e..ece5791c9a4 100644 --- a/ts/features/bonus/cgn/saga/networking/eyca/activation/getEycaActivationSaga.ts +++ b/ts/features/bonus/cgn/saga/networking/eyca/activation/getEycaActivationSaga.ts @@ -15,6 +15,7 @@ import { } from "../../../../../../../utils/errors"; import { StatusEnum } from "../../../../../../../../definitions/cgn/EycaActivationDetail"; import { cgnEycaActivation } from "../../../../store/actions/eyca/activation"; +import { withRefreshApiCall } from "../../../../../../fastLogin/saga/utils"; // wait time between requests const cgnResultPolling = 1000 as Millisecond; @@ -37,9 +38,12 @@ export function* handleStartActivation( startEycaActivation: ReturnType["startEycaActivation"] ): Generator, any> { try { - const startEycaActivationResult: SagaCallReturnType< - typeof startEycaActivation - > = yield* call(startEycaActivation, {}); + const startEycaActivationRequest = startEycaActivation({}); + const startEycaActivationResult = (yield* call( + withRefreshApiCall, + startEycaActivationRequest, + cgnEycaActivation.request() + )) as unknown as SagaCallReturnType; if (E.isRight(startEycaActivationResult)) { const status = startEycaActivationResult.right.status; const activationStatus = mapStatus.get(status); @@ -66,9 +70,12 @@ export function* getActivation( getEycaActivation: ReturnType["getEycaActivation"] ): Generator, any> { try { - const getEycaActivationResult: SagaCallReturnType< - typeof getEycaActivation - > = yield* call(getEycaActivation, {}); + const getEycaActivationRequest = getEycaActivation({}); + const getEycaActivationResult = (yield* call( + withRefreshApiCall, + getEycaActivationRequest, + cgnEycaActivation.request() + )) as unknown as SagaCallReturnType; if (E.isRight(getEycaActivationResult)) { if (getEycaActivationResult.right.status === 200) { const result = getEycaActivationResult.right.value; diff --git a/ts/features/bonus/cgn/saga/networking/eyca/details/getEycaStatus.ts b/ts/features/bonus/cgn/saga/networking/eyca/details/getEycaStatus.ts index 0dc6407da97..36b2010b6e8 100644 --- a/ts/features/bonus/cgn/saga/networking/eyca/details/getEycaStatus.ts +++ b/ts/features/bonus/cgn/saga/networking/eyca/details/getEycaStatus.ts @@ -1,3 +1,4 @@ +import { ActionType } from "typesafe-actions"; import * as E from "fp-ts/lib/Either"; import { call, put } from "typed-redux-saga/macro"; import { @@ -12,6 +13,7 @@ import { readablePrivacyReport } from "../../../../../../../utils/reporters"; import { BackendCGN } from "../../../../api/backendCgn"; import { cgnEycaStatus } from "../../../../store/actions/eyca/details"; import { EycaDetailKOStatus } from "../../../../store/reducers/eyca/details"; +import { withRefreshApiCall } from "../../../../../../fastLogin/saga/utils"; const eycaStatusMap: Record = { 403: "INELIGIBLE", @@ -29,11 +31,16 @@ const eycaStatusMap: Record = { * @param getEycaStatus */ export function* handleGetEycaStatus( - getEycaStatus: ReturnType["getEycaStatus"] + getEycaStatus: ReturnType["getEycaStatus"], + getEycaStatusAction: ActionType<(typeof cgnEycaStatus)["request"]> ): Generator { try { - const eycaInformationResult: SagaCallReturnType = - yield* call(getEycaStatus, {}); + const eycaInformationRequest = getEycaStatus({}); + const eycaInformationResult = (yield* call( + withRefreshApiCall, + eycaInformationRequest, + getEycaStatusAction + )) as unknown as SagaCallReturnType; if (E.isLeft(eycaInformationResult)) { yield* put( cgnEycaStatus.failure( diff --git a/ts/features/bonus/cgn/saga/networking/merchants/cgnMerchantsSaga.ts b/ts/features/bonus/cgn/saga/networking/merchants/cgnMerchantsSaga.ts index 8e721b1a11e..b5b1f395792 100644 --- a/ts/features/bonus/cgn/saga/networking/merchants/cgnMerchantsSaga.ts +++ b/ts/features/bonus/cgn/saga/networking/merchants/cgnMerchantsSaga.ts @@ -12,6 +12,7 @@ import { cgnOnlineMerchants, cgnSelectedMerchant } from "../../../store/actions/merchants"; +import { withRefreshApiCall } from "../../../../../fastLogin/saga/utils"; export function* cgnOnlineMerchantsSaga( getOnlineMerchants: ReturnType< @@ -20,10 +21,14 @@ export function* cgnOnlineMerchantsSaga( cgnOnlineMerchantRequest: ReturnType ) { try { - const onlineMerchantsResult: SagaCallReturnType = - yield* call(getOnlineMerchants, { - body: cgnOnlineMerchantRequest.payload - }); + const onlineMerchantsRequest = getOnlineMerchants({ + body: cgnOnlineMerchantRequest.payload + }); + const onlineMerchantsResult = (yield* call( + withRefreshApiCall, + onlineMerchantsRequest, + cgnOnlineMerchantRequest + )) as unknown as SagaCallReturnType; if (E.isLeft(onlineMerchantsResult)) { yield* put( @@ -40,6 +45,9 @@ export function* cgnOnlineMerchantsSaga( ); return; } + if (onlineMerchantsResult.right.status === 401) { + return; + } throw new Error(`Response in status ${onlineMerchantsResult.right.status}`); } catch (e) { @@ -54,11 +62,14 @@ export function* cgnOfflineMerchantsSaga( cgnOfflineMerchantRequest: ReturnType ) { try { - const offlineMerchantsResult: SagaCallReturnType< - typeof getOfflineMerchants - > = yield* call(getOfflineMerchants, { + const offlineMerchantRequest = getOfflineMerchants({ body: cgnOfflineMerchantRequest.payload }); + const offlineMerchantsResult = (yield* call( + withRefreshApiCall, + offlineMerchantRequest, + cgnOfflineMerchantRequest + )) as unknown as SagaCallReturnType; if (E.isLeft(offlineMerchantsResult)) { yield* put( @@ -77,6 +88,9 @@ export function* cgnOfflineMerchantsSaga( ); return; } + if (offlineMerchantsResult.right.status === 401) { + return; + } throw new Error( `Response in status ${offlineMerchantsResult.right.status}` @@ -91,8 +105,14 @@ export function* cgnMerchantDetail( merchantSelected: ReturnType<(typeof cgnSelectedMerchant)["request"]> ) { try { - const merchantDetailResult: SagaCallReturnType = - yield* call(getMerchant, { merchantId: merchantSelected.payload }); + const merchantDetailRequest = getMerchant({ + merchantId: merchantSelected.payload + }); + const merchantDetailResult = (yield* call( + withRefreshApiCall, + merchantDetailRequest, + merchantSelected + )) as unknown as SagaCallReturnType; if (E.isLeft(merchantDetailResult)) { yield* put( cgnSelectedMerchant.failure( @@ -107,6 +127,10 @@ export function* cgnMerchantDetail( return; } + if (merchantDetailResult.right.status === 401) { + return; + } + throw new Error(`Response in status ${merchantDetailResult.right.status}`); } catch (e) { yield* put(cgnSelectedMerchant.failure(getNetworkError(e))); diff --git a/ts/features/bonus/cgn/saga/networking/otp/index.ts b/ts/features/bonus/cgn/saga/networking/otp/index.ts index 9d6e78ec2cc..b981813acdd 100644 --- a/ts/features/bonus/cgn/saga/networking/otp/index.ts +++ b/ts/features/bonus/cgn/saga/networking/otp/index.ts @@ -1,3 +1,4 @@ +import { ActionType } from "typesafe-actions"; import * as E from "fp-ts/lib/Either"; import { call, put } from "typed-redux-saga/macro"; import { SagaCallReturnType } from "../../../../../../types/utils"; @@ -5,14 +6,20 @@ import { getNetworkError } from "../../../../../../utils/errors"; import { readablePrivacyReport } from "../../../../../../utils/reporters"; import { BackendCGN } from "../../../api/backendCgn"; import { cgnGenerateOtp as cgnGenerateOtpAction } from "../../../store/actions/otp"; +import { withRefreshApiCall } from "../../../../../fastLogin/saga/utils"; // handle the request for CGN Otp code generation export function* cgnGenerateOtp( - generateOtp: ReturnType["generateOtp"] + generateOtp: ReturnType["generateOtp"], + action: ActionType<(typeof cgnGenerateOtpAction)["request"]> ) { try { - const generateOtpResult: SagaCallReturnType = - yield* call(generateOtp, {}); + const generateOtpRequest = generateOtp({}); + const generateOtpResult = (yield* call( + withRefreshApiCall, + generateOtpRequest, + action + )) as unknown as SagaCallReturnType; if (E.isRight(generateOtpResult)) { if (generateOtpResult.right.status === 200) { yield* put(cgnGenerateOtpAction.success(generateOtpResult.right.value)); diff --git a/ts/features/bonus/cgn/saga/networking/unsubscribe/index.ts b/ts/features/bonus/cgn/saga/networking/unsubscribe/index.ts index 5a2e143470b..72d8b1acd39 100644 --- a/ts/features/bonus/cgn/saga/networking/unsubscribe/index.ts +++ b/ts/features/bonus/cgn/saga/networking/unsubscribe/index.ts @@ -1,3 +1,4 @@ +import { ActionType } from "typesafe-actions"; import * as E from "fp-ts/lib/Either"; import { call, put } from "typed-redux-saga/macro"; import { SagaCallReturnType } from "../../../../../../types/utils"; @@ -8,17 +9,22 @@ import { import { readablePrivacyReport } from "../../../../../../utils/reporters"; // handle the request for CGN unsubscription import { BackendCGN } from "../../../api/backendCgn"; import { cgnUnsubscribe } from "../../../store/actions/unsubscribe"; +import { withRefreshApiCall } from "../../../../../fastLogin/saga/utils"; // handle the request for CGN unsubscription export function* cgnUnsubscriptionHandler( startCgnUnsubscription: ReturnType< typeof BackendCGN - >["startCgnUnsubscription"] + >["startCgnUnsubscription"], + action: ActionType<(typeof cgnUnsubscribe)["request"]> ) { try { - const unsubscriptionResult: SagaCallReturnType< - typeof startCgnUnsubscription - > = yield* call(startCgnUnsubscription, {}); + const unsubscriptionRequest = startCgnUnsubscription({}); + const unsubscriptionResult = (yield* call( + withRefreshApiCall, + unsubscriptionRequest, + action + )) as unknown as SagaCallReturnType; if (E.isRight(unsubscriptionResult)) { if ( unsubscriptionResult.right.status === 201 ||