Skip to content

Commit 709a57f

Browse files
authored
feat: added wallet endpoint
Refs: #1170 SIW-1775
1 parent 6ffd4f3 commit 709a57f

File tree

6 files changed

+242
-1
lines changed

6 files changed

+242
-1
lines changed

api_io_wallet.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,22 @@ paths:
8383
$ref: "#/components/responses/ServiceUnavailable"
8484

8585
/wallet-instances/current/status:
86+
get:
87+
summary: Retrieve the current Wallet Instance status
88+
operationId: getCurrentWalletInstanceStatus
89+
responses:
90+
"200":
91+
description: Wallet Instance status successfully retrieved
92+
content:
93+
application/json:
94+
schema:
95+
$ref: "#/components/schemas/WalletInstanceData"
96+
"404":
97+
$ref: "#/components/responses/NotFound"
98+
"500":
99+
$ref: "#/components/responses/Unexpected"
100+
"503":
101+
$ref: "#/components/responses/ServiceUnavailable"
86102
put:
87103
summary: Revoke current Wallet Instance
88104
operationId: setCurrentWalletInstanceStatus
@@ -224,6 +240,18 @@ components:
224240
required:
225241
- status
226242

243+
WalletInstanceData:
244+
description: |-
245+
Describes the status of the wallet.
246+
type: object
247+
properties:
248+
id:
249+
type: string
250+
is_revoked:
251+
type: boolean
252+
required:
253+
- id
254+
- is_revoked
227255

228256
ProblemDetail:
229257
type: object

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"generate:proxy:api-session": "rimraf generated/session && gen-api-models --api-spec api_session.yaml --out-dir generated/session",
6363
"generate:lollipop-definitions": "rimraf generated/lollipop && gen-api-models --api-spec openapi/lollipop_definitions.yaml --out-dir generated/lollipop",
6464
"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",
65-
"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",
65+
"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",
6666
"generate:proxy:io-wallet-models": "rimraf generated/io-wallet && gen-api-models --api-spec api_io_wallet.yaml --out-dir generated/io-wallet",
6767
"postversion": "git push && git push --tags",
6868
"dist:modules": "modclean -r -n default:safe && yarn install --production",

src/app.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1475,6 +1475,15 @@ function registerIoWalletAPIRoutes(
14751475
ioWalletController
14761476
)
14771477
);
1478+
1479+
app.get(
1480+
`${basePath}/wallet-instances/current/status`,
1481+
bearerSessionTokenAuth,
1482+
toExpressHandler(
1483+
ioWalletController.getCurrentWalletInstanceStatus,
1484+
ioWalletController
1485+
)
1486+
);
14781487
}
14791488

14801489
export default defaultModule;

src/controllers/ioWalletController.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { CreateWalletAttestationBody } from "../../generated/io-wallet/CreateWal
3333
import { WalletAttestationView } from "../../generated/io-wallet/WalletAttestationView";
3434
import { FF_IO_WALLET_TRIAL_ENABLED } from "../config";
3535
import { SetCurrentWalletInstanceStatusBody } from "../../generated/io-wallet/SetCurrentWalletInstanceStatusBody";
36+
import { WalletInstanceData } from "../../generated/io-wallet/WalletInstanceData";
3637

3738
const toValidationError = (errors: Errors) =>
3839
ResponseErrorValidation(
@@ -158,6 +159,30 @@ export default class IoWalletController {
158159
)()
159160
);
160161

162+
/**
163+
* Get current Wallet Instance status.
164+
*/
165+
public readonly getCurrentWalletInstanceStatus = (
166+
req: express.Request
167+
): Promise<
168+
| IResponseErrorInternal
169+
| IResponseSuccessJson<WalletInstanceData>
170+
| IResponseErrorNotFound
171+
| IResponseErrorInternal
172+
| IResponseErrorServiceUnavailable
173+
| IResponseErrorValidation
174+
| IResponseErrorForbiddenNotAuthorized
175+
> =>
176+
withUserFromRequest(req, async (user) =>
177+
pipe(
178+
this.ensureFiscalCodeIsAllowed(user.fiscal_code),
179+
TE.map(() =>
180+
this.ioWalletService.getCurrentWalletInstanceStatus(user.fiscal_code)
181+
),
182+
TE.toUnion
183+
)()
184+
);
185+
161186
private readonly ensureUserIsAllowed = (
162187
userId: NonEmptyString
163188
): TE.TaskEither<Error, void> =>

src/services/__tests__/ioWalletService.test.ts

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ mockSetCurrentWalletInstanceStatus.mockImplementation(() =>
5858
})
5959
);
6060

61+
mockGetCurrentWalletInstanceStatus.mockImplementation(() =>
62+
t.success({
63+
status: 200,
64+
value: {
65+
id: "foo",
66+
is_revoked: "false",
67+
},
68+
})
69+
);
70+
6171
const api = {
6272
getEntityConfiguration: mockGetEntityConfiguration,
6373
getNonce: mockGetNonce,
@@ -510,6 +520,132 @@ describe("IoWalletService#setCurrentWalletInstanceStatus", () => {
510520
});
511521
});
512522

523+
describe("IoWalletService#getCurrentWalletInstanceStatus", () => {
524+
beforeEach(() => {
525+
jest.clearAllMocks();
526+
});
527+
528+
it("should make the correct api call", async () => {
529+
const service = new IoWalletService(api, trialSystemApi);
530+
531+
await service.getCurrentWalletInstanceStatus(aFiscalCode);
532+
533+
expect(mockGetCurrentWalletInstanceStatus).toHaveBeenCalledWith({
534+
"fiscal-code": aFiscalCode,
535+
});
536+
});
537+
538+
it("should handle a success response", async () => {
539+
const service = new IoWalletService(api, trialSystemApi);
540+
541+
const res = await service.getCurrentWalletInstanceStatus(aFiscalCode);
542+
543+
expect(res).toMatchObject({
544+
kind: "IResponseSuccessJson",
545+
});
546+
});
547+
548+
it("should handle an internal error when the API client returns 400", async () => {
549+
mockGetCurrentWalletInstanceStatus.mockImplementationOnce(() =>
550+
t.success({ status: 400 })
551+
);
552+
553+
const service = new IoWalletService(api, trialSystemApi);
554+
555+
const res = await service.getCurrentWalletInstanceStatus(aFiscalCode);
556+
557+
expect(res).toMatchObject({
558+
kind: "IResponseErrorInternal",
559+
});
560+
});
561+
562+
it("should handle a not found error when the API client returns 404", async () => {
563+
mockGetCurrentWalletInstanceStatus.mockImplementationOnce(() =>
564+
t.success({ status: 404 })
565+
);
566+
567+
const service = new IoWalletService(api, trialSystemApi);
568+
569+
const res = await service.getCurrentWalletInstanceStatus(aFiscalCode);
570+
571+
expect(res).toMatchObject({
572+
kind: "IResponseErrorNotFound",
573+
});
574+
});
575+
576+
it("should handle an internal error when the API client returns 422", async () => {
577+
mockGetCurrentWalletInstanceStatus.mockImplementationOnce(() =>
578+
t.success({ status: 422 })
579+
);
580+
581+
const service = new IoWalletService(api, trialSystemApi);
582+
583+
const res = await service.getCurrentWalletInstanceStatus(aFiscalCode);
584+
585+
expect(res).toMatchObject({
586+
kind: "IResponseErrorInternal",
587+
});
588+
});
589+
590+
it("should handle an internal error when the API client returns 500", async () => {
591+
const aGenericProblem = {};
592+
mockGetCurrentWalletInstanceStatus.mockImplementationOnce(() =>
593+
t.success({ status: 500, value: aGenericProblem })
594+
);
595+
596+
const service = new IoWalletService(api, trialSystemApi);
597+
598+
const res = await service.getCurrentWalletInstanceStatus(aFiscalCode);
599+
600+
expect(res).toMatchObject({
601+
kind: "IResponseErrorInternal",
602+
});
603+
});
604+
605+
it("should handle a service unavailable error when the API client returns 503", async () => {
606+
const aGenericProblem = {};
607+
mockGetCurrentWalletInstanceStatus.mockImplementationOnce(() =>
608+
t.success({ status: 503, value: aGenericProblem })
609+
);
610+
611+
const service = new IoWalletService(api, trialSystemApi);
612+
613+
const res = await service.getCurrentWalletInstanceStatus(aFiscalCode);
614+
615+
expect(res).toMatchObject({
616+
kind: "IResponseErrorServiceUnavailable",
617+
});
618+
});
619+
620+
it("should handle an internal error when the API client returns a code not specified in spec", async () => {
621+
const aGenericProblem = {};
622+
mockGetCurrentWalletInstanceStatus.mockImplementationOnce(() =>
623+
t.success({ status: 599, value: aGenericProblem })
624+
);
625+
626+
const service = new IoWalletService(api, trialSystemApi);
627+
628+
const res = await service.getCurrentWalletInstanceStatus(aFiscalCode);
629+
630+
expect(res).toMatchObject({
631+
kind: "IResponseErrorInternal",
632+
});
633+
});
634+
635+
it("should return an error if the api call throws an error", async () => {
636+
mockGetCurrentWalletInstanceStatus.mockImplementationOnce(() => {
637+
throw new Error();
638+
});
639+
const service = new IoWalletService(api, trialSystemApi);
640+
641+
const res = await service.getCurrentWalletInstanceStatus(aFiscalCode);
642+
643+
expect(res).toMatchObject({
644+
kind: "IResponseErrorInternal",
645+
});
646+
});
647+
});
648+
513649
describe("IoWalletService#getSubscription", () => {
514650
beforeEach(() => {
515651
jest.clearAllMocks();

src/services/ioWalletService.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { TrialSystemAPIClient } from "../clients/trial-system.client";
3636
import { Subscription } from "../../generated/trial-system-api/Subscription";
3737
import { WalletAttestationView } from "../../generated/io-wallet-api/WalletAttestationView";
3838
import { SetWalletInstanceStatusWithFiscalCodeData } from "../../generated/io-wallet-api/SetWalletInstanceStatusWithFiscalCodeData";
39+
import { WalletData } from "../../generated/io-wallet-api/WalletData";
3940

4041
const unprocessableContentError = "Unprocessable Content";
4142
const invalidRequest = "Your request didn't validate";
@@ -242,6 +243,48 @@ export default class IoWalletService {
242243
});
243244
});
244245

246+
/**
247+
* Get current Wallet Instance status.
248+
*/
249+
public readonly getCurrentWalletInstanceStatus = (
250+
fiscal_code: FiscalCode
251+
): Promise<
252+
| IResponseSuccessJson<WalletData>
253+
| IResponseErrorNotFound
254+
| IResponseErrorInternal
255+
| IResponseErrorServiceUnavailable
256+
> =>
257+
withCatchAsInternalError(async () => {
258+
const validated =
259+
await this.ioWalletApiClient.getCurrentWalletInstanceStatus({
260+
"fiscal-code": fiscal_code,
261+
});
262+
return withValidatedOrInternalError(validated, (response) => {
263+
switch (response.status) {
264+
case 200:
265+
return ResponseSuccessJson(response.value);
266+
case 404:
267+
return ResponseErrorNotFound(
268+
"Not Found",
269+
"Wallet instance not found"
270+
);
271+
case 400:
272+
case 422:
273+
case 500:
274+
return ResponseErrorInternal(
275+
`Internal server error | ${response.value}`
276+
);
277+
case 503:
278+
return ResponseErrorServiceTemporarilyUnavailable(
279+
serviceUnavailableDetail,
280+
"10"
281+
);
282+
default:
283+
return ResponseErrorStatusNotDefinedInSpec(response);
284+
}
285+
});
286+
});
287+
245288
/**
246289
* Get the subscription given a specific user.
247290
*/

0 commit comments

Comments
 (0)