teamdigitale/italia-app

View on GitHub
ts/store/reducers/wallet/__tests__/wallets.test.ts

Summary

Maintainability
F
4 days
Test Coverage
import * as E from "fp-ts/lib/Either";
import { Errors } from "io-ts";
import * as pot from "@pagopa/ts-commons/lib/pot";
import _ from "lodash";
import { pipe } from "fp-ts/lib/function";
import { remoteUndefined } from "../../../../common/model/RemoteValue";
import {
  CreditCard,
  isRawCreditCard,
  PatchedWalletV2ListResponse,
  RawPaymentMethod,
  Wallet
} from "../../../../types/pagopa";
import { walletsV2_2, walletsV2_1, walletsV2_3 } from "../__mocks__/wallets";
import { toIndexed } from "../../../helpers/indexer";
import {
  bancomatListSelector,
  bPayListSelector,
  creditCardListSelector,
  creditCardWalletV1Selector,
  getFavoriteWallet,
  getFavoriteWalletId,
  withPaymentFeatureSelector,
  getWalletsById,
  pagoPaCreditCardWalletV1Selector,
  updatingFavouriteWalletSelector
} from "../wallets";
import { GlobalState } from "../../types";
import { convertWalletV2toWalletV1 } from "../../../../utils/walletv2";
import { getPaymentMethodHash } from "../../../../utils/paymentMethod";
import { appReducer } from "../../index";
import { applicationChangeState } from "../../../actions/application";
import {
  fetchWalletsSuccess,
  setFavouriteWalletFailure,
  setFavouriteWalletRequest,
  setFavouriteWalletSuccess,
  updatePaymentStatus
} from "../../../actions/wallet/wallets";
import { EnableableFunctionsEnum } from "../../../../../definitions/pagopa/EnableableFunctions";
import { deleteAllPaymentMethodsByFunction } from "../../../actions/wallet/delete";
import { TypeEnum } from "../../../../../definitions/pagopa/Wallet";
import {
  CreditCardExpirationMonth,
  CreditCardExpirationYear,
  CreditCardPan
} from "../../../../utils/input";

describe("walletV2 selectors", () => {
  const maybeWalletsV2 = PatchedWalletV2ListResponse.decode(walletsV2_1);
  const indexedWallets = toIndexed(
    pipe(
      maybeWalletsV2,
      E.map(walletsV2 => walletsV2.data!.map(convertWalletV2toWalletV1)),
      E.getOrElseW(() => [])
    ),
    w => w.idWallet
  );
  const globalState = {
    wallet: {
      wallets: {
        walletById: pot.some(indexedWallets)
      },
      abi: remoteUndefined
    }
  } as any as GlobalState;
  it("should decode walletv2 list", () => {
    expect(E.isRight(maybeWalletsV2)).toBeTruthy();
  });

  it("should return credit cards", () => {
    const maybeCC = creditCardWalletV1Selector(globalState);
    expect(pot.isSome(maybeCC)).toBeTruthy();
    if (pot.isSome(maybeCC)) {
      expect(maybeCC.value.length).toEqual(1);
      const paymentMethod = maybeCC.value[0].paymentMethod;
      if (paymentMethod) {
        expect(isRawCreditCard(paymentMethod)).toBeTruthy();
        expect(getPaymentMethodHash(paymentMethod)).toEqual(
          "853afb770973eb48d5d275778bd124b28f60a684c20bcdf05dc8f0014c7ce871"
        );
      }
    }
  });

  it("should return bancomat", () => {
    const maybeBancomat = bancomatListSelector(globalState);
    expect(pot.isSome(maybeBancomat)).toBeTruthy();
    const hpans = [
      "a591ab131bd9492e6df0357f1ac52785a96ddc8e772baddbb02e2169af9474f4",
      "e105a87731025d54181d8e4c4c04ff344ce82e57d6a3d6c6911e8eadb0348d7b"
    ];
    if (pot.isSome(maybeBancomat)) {
      expect(maybeBancomat.value.length).toEqual(2);
      maybeBancomat.value.forEach(w => {
        expect(hpans.find(h => h === getPaymentMethodHash(w))).toBeDefined();
      });
    }
  });

  it("should return credit card supporting pagoPa payments", () => {
    const maybePagoPaCC = pagoPaCreditCardWalletV1Selector(globalState);
    expect(pot.isSome(maybePagoPaCC)).toBeTruthy();
    if (pot.isSome(maybePagoPaCC)) {
      expect(maybePagoPaCC.value.length).toEqual(1);
      const paymentMethod = maybePagoPaCC.value[0].paymentMethod;
      if (paymentMethod) {
        expect(isRawCreditCard(paymentMethod)).toBeTruthy();
        expect(getPaymentMethodHash(paymentMethod)).toEqual(
          "853afb770973eb48d5d275778bd124b28f60a684c20bcdf05dc8f0014c7ce871"
        );
      }
    }
  });

  it("should return empty list since there is no method compliant with pagoPa", () => {
    const maybeWallets = PatchedWalletV2ListResponse.decode(walletsV2_2);
    const globalState = mockWalletState(maybeWallets);
    const maybePagoPaCC = pagoPaCreditCardWalletV1Selector(globalState);
    expect(pot.isSome(maybePagoPaCC)).toBeTruthy();
    if (pot.isSome(maybePagoPaCC)) {
      expect(maybePagoPaCC.value.length).toEqual(0);
    }
  });
  it("should filter credit card and return one", () => {
    const maybeWallets = PatchedWalletV2ListResponse.decode(walletsV2_3);
    const globalState = mockWalletState(maybeWallets);
    const potCreditCard = creditCardListSelector(globalState);
    expect(pot.isSome(potCreditCard)).toBeTruthy();
    expect(pot.getOrElse(potCreditCard, undefined)).toBeDefined();
    if (pot.isSome(potCreditCard)) {
      expect(potCreditCard.value.length).toEqual(1);
    }
  });
  it("should filter bancomat and return one", () => {
    const maybeWallets = PatchedWalletV2ListResponse.decode(walletsV2_3);
    const globalState = mockWalletState(maybeWallets);
    const potBancomat = bancomatListSelector(globalState);
    expect(pot.isSome(potBancomat)).toBeTruthy();
    expect(pot.getOrElse(potBancomat, undefined)).toBeDefined();
    if (pot.isSome(potBancomat)) {
      expect(potBancomat.value.length).toEqual(1);
    }
  });
  it("should filter BPay and return one", () => {
    const maybeWallets = PatchedWalletV2ListResponse.decode(walletsV2_3);
    const globalState = mockWalletState(maybeWallets);
    const potBPay = bPayListSelector(globalState);
    expect(pot.isSome(potBPay)).toBeTruthy();
    expect(pot.getOrElse(potBPay, undefined)).toBeDefined();
    if (pot.isSome(potBPay)) {
      expect(potBPay.value.length).toEqual(1);
    }
  });
});

describe("walletV2 favoriteId Selector", () => {
  const maybeWalletsV2 = PatchedWalletV2ListResponse.decode(walletsV2_1);
  // set all method to not favourite
  const indexedWallets = toIndexed(
    pipe(
      maybeWalletsV2,
      E.map(walletsV2 =>
        walletsV2
          .data!.map(convertWalletV2toWalletV1)
          .map(w => ({ ...w, favourite: false }))
      ),
      E.getOrElseW(() => [])
    ),
    w => w.idWallet
  );

  it("should return pot none - no wallets", () => {
    const noWallets = {
      wallet: {
        wallets: {
          walletById: pot.none
        }
      }
    } as any as GlobalState;
    expect(getFavoriteWalletId(noWallets)).toEqual(pot.none);
    expect(getFavoriteWallet(noWallets)).toEqual(pot.none);
  });

  it("should return pot none - no favourite method", () => {
    const noFavouriteState = {
      wallet: {
        wallets: {
          walletById: pot.some(indexedWallets)
        }
      }
    } as GlobalState;
    expect(getFavoriteWalletId(noFavouriteState)).toEqual(pot.none);
    expect(getFavoriteWallet(noFavouriteState)).toEqual(pot.none);
  });

  it("should return the favourite wallet id", () => {
    const firstKey = _.keys(indexedWallets)[0];
    const favouriteWallet: Wallet = {
      ...(indexedWallets[firstKey] as Wallet),
      favourite: true
    };
    const aFavourite = _.update(
      indexedWallets,
      firstKey,
      () => favouriteWallet
    );
    const aFavoriteState = {
      wallet: {
        wallets: {
          walletById: pot.some(aFavourite)
        }
      }
    } as GlobalState;
    expect(getFavoriteWalletId(aFavoriteState)).toEqual(
      pot.some(favouriteWallet.idWallet)
    );
    expect(getFavoriteWallet(aFavoriteState)).toEqual(
      pot.some(favouriteWallet)
    );
  });
});

describe("updatePaymentStatus state changes", () => {
  const walletsV2 = pipe(
    walletsV2_1,
    PatchedWalletV2ListResponse.decode,
    E.getOrElseW(() => [] as PatchedWalletV2ListResponse)
  );
  const globalState = appReducer(undefined, applicationChangeState("active"));
  const withWallets = appReducer(
    globalState,
    fetchWalletsSuccess(walletsV2.data!.map(convertWalletV2toWalletV1))
  );
  expect(pot.isSome(withWallets.wallet.wallets.walletById)).toBeTruthy();
  if (pot.isSome(withWallets.wallet.wallets.walletById)) {
    const currentIndexedWallets = withWallets.wallet.wallets.walletById.value;
    expect(Object.keys(currentIndexedWallets).length).toEqual(
      walletsV2.data!.length
    );
    // try to invert payment status on first wallet
    const temp = Object.values(currentIndexedWallets)[0];
    const firstWallet = {
      ...temp,
      paymentMethod: {
        ...temp!.paymentMethod,
        pagoPA: true
      } as RawPaymentMethod
    } as Wallet;
    const updatePaymentStatusState = appReducer(
      globalState,
      updatePaymentStatus.success({
        ...firstWallet,
        pagoPA: false
      } as any)
    );
    const updatedState = updatePaymentStatusState.wallet.wallets.walletById;
    expect(pot.isSome(updatedState)).toBeTruthy();
    if (pot.isSome(updatedState)) {
      const updatedFirstWallet = Object.values(updatedState.value).find(
        w => w!.idWallet === firstWallet.idWallet
      );
      expect(updatedFirstWallet!.paymentMethod!.pagoPA).toBeTruthy();
    }
  }
});

describe("getPayablePaymentMethodsSelector", () => {
  it("should return false - no payable methods", () => {
    const withWallets = appReducer(undefined, fetchWalletsSuccess([]));
    expect(withPaymentFeatureSelector(withWallets).length).toEqual(0);
  });

  it("should return false - empty wallet", () => {
    const paymentMethods = pipe(
      walletsV2_1,
      PatchedWalletV2ListResponse.decode,
      E.getOrElseW(() => [] as PatchedWalletV2ListResponse)
    );

    const updatedMethods = paymentMethods.data!.map(w =>
      convertWalletV2toWalletV1({ ...w, enableableFunctions: [] })
    );
    const withWallets = appReducer(
      undefined,
      fetchWalletsSuccess(updatedMethods)
    );
    expect(updatedMethods.length).toBeGreaterThan(0);
    expect(withPaymentFeatureSelector(withWallets).length).toEqual(0);
  });

  it("should return true - one payable method", () => {
    const paymentMethods = pipe(
      walletsV2_1,
      PatchedWalletV2ListResponse.decode,
      E.getOrElseW(() => [] as PatchedWalletV2ListResponse)
    );
    const updatedMethods = [...paymentMethods.data!];
    // eslint-disable-next-line functional/immutable-data
    updatedMethods[0] = {
      ...updatedMethods[0],
      pagoPA: true,
      enableableFunctions: [EnableableFunctionsEnum.pagoPA]
    };
    const withWallets = appReducer(
      undefined,
      fetchWalletsSuccess(updatedMethods.map(convertWalletV2toWalletV1))
    );
    expect(updatedMethods.length).toBeGreaterThan(0);
    expect(withPaymentFeatureSelector(withWallets).length).toBeGreaterThan(0);
  });
});

describe("getPagoPAMethodsSelector", () => {
  it("should return false - no payable methods", () => {
    const withWallets = appReducer(undefined, fetchWalletsSuccess([]));
    expect(withPaymentFeatureSelector(withWallets).length).toEqual(0);
  });

  it("should return true - one pagoPA method", () => {
    const paymentMethods = pipe(
      walletsV2_1,
      PatchedWalletV2ListResponse.decode,
      E.getOrElseW(() => [] as PatchedWalletV2ListResponse)
    );
    const updatedMethods = [...paymentMethods.data!];
    // eslint-disable-next-line functional/immutable-data
    updatedMethods[0] = {
      ...updatedMethods[0],
      enableableFunctions: [EnableableFunctionsEnum.pagoPA]
    };
    const withWallets = appReducer(
      undefined,
      fetchWalletsSuccess(updatedMethods.map(convertWalletV2toWalletV1))
    );
    expect(updatedMethods.length).toBeGreaterThan(0);
    expect(withPaymentFeatureSelector(withWallets).length).toBeGreaterThan(0);
  });
});

describe("updatingFavouriteWalletSelector", () => {
  it("when empty should return pot.none", () => {
    const empty = appReducer(undefined, applicationChangeState("active"));
    expect(updatingFavouriteWalletSelector(empty)).toEqual(pot.none);
  });

  it("when a favourite setting request is dispatch, should return pot.someLoading", () => {
    const empty = appReducer(undefined, setFavouriteWalletRequest(99));
    expect(updatingFavouriteWalletSelector(empty)).toEqual(
      pot.noneUpdating(99)
    );
  });

  it("when a favourite setting request has been successfully, should return pot.some", () => {
    const updatedWallet = {
      idWallet: 99,
      type: TypeEnum.CREDIT_CARD,
      favourite: true,
      creditCard: {
        id: 3,
        holder: "Gian Maria Rossi",
        pan: "************0000" as CreditCardPan,
        expireMonth: "09" as CreditCardExpirationMonth,
        expireYear: "22" as CreditCardExpirationYear,
        brandLogo:
          "https://wisp2.pagopa.gov.it/wallet/assets/img/creditcard/carta_poste.png",
        flag3dsVerified: false,
        brand: "POSTEPAY",
        onUs: false
      } as CreditCard,
      pspEditable: true,
      isPspToIgnore: false,
      saved: false,
      registeredNexi: false
    };
    const empty = appReducer(
      undefined,
      setFavouriteWalletSuccess(updatedWallet as Wallet)
    );
    expect(updatingFavouriteWalletSelector(empty)).toEqual(pot.some(99));
  });

  it("when a favourite setting request has been failed, should return pot.someError", () => {
    const empty = appReducer(undefined, setFavouriteWalletRequest(99));
    const state = appReducer(
      empty,
      setFavouriteWalletFailure(new Error("setFavouriteWalletFailure error"))
    );
    expect(updatingFavouriteWalletSelector(state)).toEqual(
      pot.noneError(new Error("setFavouriteWalletFailure error"))
    );
  });
});

describe("walletV2 reducer - deleteAllByFunction", () => {
  it("should delete only those payment method whose have specified function enabled", () => {
    const aPaymentMethod = walletsV2_1.data[0];
    // 2 pagoPA + 1 BPD
    const wallet = [
      {
        ...aPaymentMethod,
        idWallet: 1,
        enableableFunctions: [EnableableFunctionsEnum.pagoPA]
      },
      {
        ...aPaymentMethod,
        idWallet: 2,
        enableableFunctions: [EnableableFunctionsEnum.pagoPA]
      },
      {
        ...aPaymentMethod,
        idWallet: 3,
        enableableFunctions: []
      }
    ];
    const maybeWalletsV2 = PatchedWalletV2ListResponse.decode({ data: wallet });
    const convertedWallets = (
      E.getOrElseW(() => [])(maybeWalletsV2) as PatchedWalletV2ListResponse
    ).data!.map(convertWalletV2toWalletV1);

    const globalState: GlobalState = appReducer(
      undefined,
      fetchWalletsSuccess(convertedWallets)
    );
    const walletFull = getWalletsById(globalState);
    expect(pot.isSome(walletFull)).toBeTruthy();
    if (pot.isSome(walletFull)) {
      expect(Object.keys(walletFull.value).length).toEqual(
        convertedWallets.length
      );
    }
    const updatedState: GlobalState = appReducer(
      globalState,
      deleteAllPaymentMethodsByFunction.success({
        wallets: convertedWallets,
        deletedMethodsCount: 1
      })
    );
    const walletUpdated = getWalletsById(updatedState);
    expect(pot.isSome(walletUpdated)).toBeTruthy();
    if (pot.isSome(walletUpdated)) {
      expect(Object.keys(walletUpdated.value).length).toEqual(
        convertedWallets.length
      );
    }
  });
});

const mockWalletState = (
  walletResponse: E.Either<Errors, PatchedWalletV2ListResponse>
) => {
  const indexedWallets = toIndexed(
    E.getOrElseW(() => [] as PatchedWalletV2ListResponse)(
      walletResponse
    ).data!.map(convertWalletV2toWalletV1),
    w => w.idWallet
  );
  return {
    wallet: {
      abi: remoteUndefined,
      wallets: {
        walletById: pot.some(indexedWallets)
      }
    }
  } as GlobalState;
};