Skip to content

Commit

Permalink
chore: added IoWalletAPIClient and IoWalletService
Browse files Browse the repository at this point in the history
Refs: #1125 SIW-1111
  • Loading branch information
silvicir authored May 27, 2024
1 parent 5cbe54b commit ffc6656
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"generate:proxy:api-session": "rimraf generated/session && gen-api-models --api-spec api_session.yaml --out-dir generated/session",
"generate:lollipop-definitions": "rimraf generated/lollipop && gen-api-models --api-spec openapi/lollipop_definitions.yaml --out-dir generated/lollipop",
"generate:lollipop-first-sign": "rimraf generated/lollipop-first-consumer && gen-api-models --api-spec openapi/consumed/lollipop_first_consumer.yaml --out-dir generated/lollipop-first-consumer --request-types --response-decoders --client",
"generate:api:io-wallet": "rimraf generated/io-wallet-api && gen-api-models --api-spec https://raw.githubusercontent.com/pagopa/io-wallet/io-wallet-user-func@0.1.3/apps/io-wallet-user-func/openapi.yaml --no-strict --out-dir generated/io-wallet-api --request-types --response-decoders --client",
"postversion": "git push && git push --tags",
"dist:modules": "modclean -r -n default:safe && yarn install --production",
"predeploy": "npm-run-all build dist:modules",
Expand Down
26 changes: 26 additions & 0 deletions src/clients/io-wallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Client, createClient } from "../../generated/io-wallet-api/client";

type Fetch = (
input: RequestInfo | URL,
init?: RequestInit | undefined
) => Promise<Response>;

export function IoWalletAPIClient(
token: string,
basePath: string,
baseUrl: string,
fetchApi: Fetch
): Client<"FunctionsKey"> {
return createClient<"FunctionsKey">({
basePath,
baseUrl,
fetchApi,
withDefaults: (op) => (params) =>
op({
...params,
FunctionsKey: token,
}),
});
}

export type IoWalletAPIClient = typeof IoWalletAPIClient;
35 changes: 35 additions & 0 deletions src/controllers/ioWalletController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* This controller handles the IO_WALLET requests from the
* app by forwarding the call to the API system.
*/

import * as TE from "fp-ts/TaskEither";
import * as E from "fp-ts/Either";

import { IResponseSuccessJson } from "@pagopa/ts-commons/lib/responses";

import { pipe } from "fp-ts/lib/function";
import { FiscalCode } from "@pagopa/ts-commons/lib/strings";
import { UserDetailView } from "generated/io-wallet-api/UserDetailView";
import IoWalletService from "../services/ioWalletService";

export const retrieveUserId = (
ioWalletService: IoWalletService,
fiscalCode: FiscalCode
) =>
pipe(
TE.tryCatch(
() => ioWalletService.getUserByFiscalCode(fiscalCode),
E.toError
),
TE.chain(
TE.fromPredicate(
(r): r is IResponseSuccessJson<UserDetailView> =>
r.kind === "IResponseSuccessJson",
(e) =>
new Error(
`An error occurred while retrieving the User id. | ${e.detail}`
)
)
)
);
115 changes: 115 additions & 0 deletions src/services/__tests__/ioWalletService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import * as t from "io-ts";
import IoWalletService from "../ioWalletService";
import { FiscalCode } from "@pagopa/ts-commons/lib/strings";

const mockGetEntityConfiguration = jest.fn();
const mockGetNonce = jest.fn();
const mockGetUserByFiscalCode = jest.fn();
const mockCreateWalletInstance = jest.fn();
const mockCreateWalletAttestation = jest.fn();
const mockHealthCheck = jest.fn();

mockGetUserByFiscalCode.mockImplementation(() =>
t.success({
status: 200,
value: {
id: "000000000000",
},
})
);

const api = {
getEntityConfiguration: mockGetEntityConfiguration,
getNonce: mockGetNonce,
getUserByFiscalCode: mockGetUserByFiscalCode,
createWalletInstance: mockCreateWalletInstance,
createWalletAttestation: mockCreateWalletAttestation,
healthCheck: mockHealthCheck,
};

const aFiscalCode = "GRBGPP87L04L741X" as FiscalCode;

describe("IoWalletService#getUserByFiscalCode", () => {
beforeEach(() => {
jest.clearAllMocks();
});

it("should make the correct api call", async () => {
const service = new IoWalletService(api);

await service.getUserByFiscalCode(aFiscalCode);

expect(mockGetUserByFiscalCode).toHaveBeenCalledWith({
body: {
fiscal_code: aFiscalCode,
},
});
});

it("should handle a success response", async () => {
const service = new IoWalletService(api);

const res = await service.getUserByFiscalCode(aFiscalCode);

expect(res).toMatchObject({
kind: "IResponseSuccessJson",
});
});

it("should handle an internal error when the API client returns 422", async () => {
mockGetUserByFiscalCode.mockImplementationOnce(() =>
t.success({ status: 422 })
);

const service = new IoWalletService(api);

const res = await service.getUserByFiscalCode(aFiscalCode);

expect(res).toMatchObject({
kind: "IResponseErrorGeneric",
});
});

it("should handle an internal error when the API client returns 500", async () => {
const aGenericProblem = {};
mockGetUserByFiscalCode.mockImplementationOnce(() =>
t.success({ status: 500, value: aGenericProblem })
);

const service = new IoWalletService(api);

const res = await service.getUserByFiscalCode(aFiscalCode);

expect(res).toMatchObject({
kind: "IResponseErrorInternal",
});
});

it("should handle an internal error when the API client returns a code not specified in spec", async () => {
const aGenericProblem = {};
mockGetUserByFiscalCode.mockImplementationOnce(() =>
t.success({ status: 599, value: aGenericProblem })
);

const service = new IoWalletService(api);

const res = await service.getUserByFiscalCode(aFiscalCode);

expect(res).toMatchObject({
kind: "IResponseErrorInternal",
});
});

it("should return an error if the api call throws an error", async () => {
mockGetUserByFiscalCode.mockImplementationOnce(() => {
throw new Error();
});
const service = new IoWalletService(api);

const res = await service.getUserByFiscalCode(aFiscalCode);

expect(res).toMatchObject({
kind: "IResponseErrorInternal",
});
});
});
61 changes: 61 additions & 0 deletions src/services/ioWalletService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* This service interacts with the IO Wallet API
*/

import {
IResponseErrorGeneric,
IResponseErrorInternal,
IResponseSuccessJson,
ResponseErrorGeneric,
ResponseErrorInternal,
ResponseSuccessJson,
} from "@pagopa/ts-commons/lib/responses";

import { FiscalCode } from "@pagopa/ts-commons/lib/strings";
import { UserDetailView } from "generated/io-wallet-api/UserDetailView";
import { IoWalletAPIClient } from "../clients/io-wallet";
import {
ResponseErrorStatusNotDefinedInSpec,
withCatchAsInternalError,
withValidatedOrInternalError,
} from "../utils/responses";

export default class IoWalletService {
constructor(
private readonly ioWalletApiClient: ReturnType<IoWalletAPIClient>
) {}

/**
* Get the Wallet User id.
*/
public readonly getUserByFiscalCode = (
fiscalCode: FiscalCode
): Promise<
| IResponseErrorInternal
| IResponseErrorGeneric
| IResponseSuccessJson<UserDetailView>
> =>
withCatchAsInternalError(async () => {
const validated = await this.ioWalletApiClient.getUserByFiscalCode({
body: { fiscal_code: fiscalCode },
});
return withValidatedOrInternalError(validated, (response) => {
switch (response.status) {
case 200:
return ResponseSuccessJson(response.value);
case 422:
return ResponseErrorGeneric(
response.status,
"Unprocessable Content",
"Your request didn't validate"
);
case 500:
return ResponseErrorInternal(
`Internal server error | ${response.value}`
);
default:
return ResponseErrorStatusNotDefinedInSpec(response);
}
});
});
}

0 comments on commit ffc6656

Please sign in to comment.