Skip to content

Commit

Permalink
feat: added wallet endpoint
Browse files Browse the repository at this point in the history
Refs: #1170 SIW-1775
  • Loading branch information
silvicir authored Nov 14, 2024
1 parent 6ffd4f3 commit 709a57f
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 1 deletion.
28 changes: 28 additions & 0 deletions api_io_wallet.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,22 @@ paths:
$ref: "#/components/responses/ServiceUnavailable"

/wallet-instances/current/status:
get:
summary: Retrieve the current Wallet Instance status
operationId: getCurrentWalletInstanceStatus
responses:
"200":
description: Wallet Instance status successfully retrieved
content:
application/json:
schema:
$ref: "#/components/schemas/WalletInstanceData"
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/Unexpected"
"503":
$ref: "#/components/responses/ServiceUnavailable"
put:
summary: Revoke current Wallet Instance
operationId: setCurrentWalletInstanceStatus
Expand Down Expand Up @@ -224,6 +240,18 @@ components:
required:
- status

WalletInstanceData:
description: |-
Describes the status of the wallet.
type: object
properties:
id:
type: string
is_revoked:
type: boolean
required:
- id
- is_revoked

ProblemDetail:
type: object
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,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@1.2.3/apps/io-wallet-user-func/openapi.yaml --no-strict --out-dir generated/io-wallet-api --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@2.0.0/apps/io-wallet-user-func/openapi.yaml --no-strict --out-dir generated/io-wallet-api --request-types --response-decoders --client",
"generate:proxy:io-wallet-models": "rimraf generated/io-wallet && gen-api-models --api-spec api_io_wallet.yaml --out-dir generated/io-wallet",
"postversion": "git push && git push --tags",
"dist:modules": "modclean -r -n default:safe && yarn install --production",
Expand Down
9 changes: 9 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1475,6 +1475,15 @@ function registerIoWalletAPIRoutes(
ioWalletController
)
);

app.get(
`${basePath}/wallet-instances/current/status`,
bearerSessionTokenAuth,
toExpressHandler(
ioWalletController.getCurrentWalletInstanceStatus,
ioWalletController
)
);
}

export default defaultModule;
25 changes: 25 additions & 0 deletions src/controllers/ioWalletController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { CreateWalletAttestationBody } from "../../generated/io-wallet/CreateWal
import { WalletAttestationView } from "../../generated/io-wallet/WalletAttestationView";
import { FF_IO_WALLET_TRIAL_ENABLED } from "../config";
import { SetCurrentWalletInstanceStatusBody } from "../../generated/io-wallet/SetCurrentWalletInstanceStatusBody";
import { WalletInstanceData } from "../../generated/io-wallet/WalletInstanceData";

const toValidationError = (errors: Errors) =>
ResponseErrorValidation(
Expand Down Expand Up @@ -158,6 +159,30 @@ export default class IoWalletController {
)()
);

/**
* Get current Wallet Instance status.
*/
public readonly getCurrentWalletInstanceStatus = (
req: express.Request
): Promise<
| IResponseErrorInternal
| IResponseSuccessJson<WalletInstanceData>
| IResponseErrorNotFound
| IResponseErrorInternal
| IResponseErrorServiceUnavailable
| IResponseErrorValidation
| IResponseErrorForbiddenNotAuthorized
> =>
withUserFromRequest(req, async (user) =>
pipe(
this.ensureFiscalCodeIsAllowed(user.fiscal_code),
TE.map(() =>
this.ioWalletService.getCurrentWalletInstanceStatus(user.fiscal_code)
),
TE.toUnion
)()
);

private readonly ensureUserIsAllowed = (
userId: NonEmptyString
): TE.TaskEither<Error, void> =>
Expand Down
136 changes: 136 additions & 0 deletions src/services/__tests__/ioWalletService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ mockSetCurrentWalletInstanceStatus.mockImplementation(() =>
})
);

mockGetCurrentWalletInstanceStatus.mockImplementation(() =>
t.success({
status: 200,
value: {
id: "foo",
is_revoked: "false",
},
})
);

const api = {
getEntityConfiguration: mockGetEntityConfiguration,
getNonce: mockGetNonce,
Expand Down Expand Up @@ -510,6 +520,132 @@ describe("IoWalletService#setCurrentWalletInstanceStatus", () => {
});
});

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

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

await service.getCurrentWalletInstanceStatus(aFiscalCode);

expect(mockGetCurrentWalletInstanceStatus).toHaveBeenCalledWith({
"fiscal-code": aFiscalCode,
});
});

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

const res = await service.getCurrentWalletInstanceStatus(aFiscalCode);

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

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

const service = new IoWalletService(api, trialSystemApi);

const res = await service.getCurrentWalletInstanceStatus(aFiscalCode);

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

it("should handle a not found error when the API client returns 404", async () => {
mockGetCurrentWalletInstanceStatus.mockImplementationOnce(() =>
t.success({ status: 404 })
);

const service = new IoWalletService(api, trialSystemApi);

const res = await service.getCurrentWalletInstanceStatus(aFiscalCode);

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

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

const service = new IoWalletService(api, trialSystemApi);

const res = await service.getCurrentWalletInstanceStatus(aFiscalCode);

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

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

const service = new IoWalletService(api, trialSystemApi);

const res = await service.getCurrentWalletInstanceStatus(aFiscalCode);

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

it("should handle a service unavailable error when the API client returns 503", async () => {
const aGenericProblem = {};
mockGetCurrentWalletInstanceStatus.mockImplementationOnce(() =>
t.success({ status: 503, value: aGenericProblem })
);

const service = new IoWalletService(api, trialSystemApi);

const res = await service.getCurrentWalletInstanceStatus(aFiscalCode);

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

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

const service = new IoWalletService(api, trialSystemApi);

const res = await service.getCurrentWalletInstanceStatus(aFiscalCode);

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

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

const res = await service.getCurrentWalletInstanceStatus(aFiscalCode);

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

describe("IoWalletService#getSubscription", () => {
beforeEach(() => {
jest.clearAllMocks();
Expand Down
43 changes: 43 additions & 0 deletions src/services/ioWalletService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { TrialSystemAPIClient } from "../clients/trial-system.client";
import { Subscription } from "../../generated/trial-system-api/Subscription";
import { WalletAttestationView } from "../../generated/io-wallet-api/WalletAttestationView";
import { SetWalletInstanceStatusWithFiscalCodeData } from "../../generated/io-wallet-api/SetWalletInstanceStatusWithFiscalCodeData";
import { WalletData } from "../../generated/io-wallet-api/WalletData";

const unprocessableContentError = "Unprocessable Content";
const invalidRequest = "Your request didn't validate";
Expand Down Expand Up @@ -242,6 +243,48 @@ export default class IoWalletService {
});
});

/**
* Get current Wallet Instance status.
*/
public readonly getCurrentWalletInstanceStatus = (
fiscal_code: FiscalCode
): Promise<
| IResponseSuccessJson<WalletData>
| IResponseErrorNotFound
| IResponseErrorInternal
| IResponseErrorServiceUnavailable
> =>
withCatchAsInternalError(async () => {
const validated =
await this.ioWalletApiClient.getCurrentWalletInstanceStatus({
"fiscal-code": fiscal_code,
});
return withValidatedOrInternalError(validated, (response) => {
switch (response.status) {
case 200:
return ResponseSuccessJson(response.value);
case 404:
return ResponseErrorNotFound(
"Not Found",
"Wallet instance not found"
);
case 400:
case 422:
case 500:
return ResponseErrorInternal(
`Internal server error | ${response.value}`
);
case 503:
return ResponseErrorServiceTemporarilyUnavailable(
serviceUnavailableDetail,
"10"
);
default:
return ResponseErrorStatusNotDefinedInSpec(response);
}
});
});

/**
* Get the subscription given a specific user.
*/
Expand Down

0 comments on commit 709a57f

Please sign in to comment.