Skip to content
This repository has been archived by the owner on Jan 15, 2025. It is now read-only.

Commit

Permalink
[#177017837] Added new api for retrieving eyca card (#25)
Browse files Browse the repository at this point in the history
* added new api for retrieving eyca card
  • Loading branch information
gquadrati authored Feb 22, 2021
1 parent 16db13b commit 70778e0
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 11 deletions.
19 changes: 8 additions & 11 deletions GetCgnStatus/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Context } from "@azure/functions";
import { fromOption } from "fp-ts/lib/Either";
import { identity } from "fp-ts/lib/function";
import { fromEither } from "fp-ts/lib/TaskEither";
import { fromLeft } from "fp-ts/lib/TaskEither";
import { ContextMiddleware } from "io-functions-commons/dist/src/utils/middlewares/context_middleware";
import { RequiredParamMiddleware } from "io-functions-commons/dist/src/utils/middlewares/required_param";
import {
Expand All @@ -21,7 +20,7 @@ import {
} from "italia-ts-commons/lib/responses";
import { FiscalCode } from "italia-ts-commons/lib/strings";
import { Card } from "../generated/definitions/Card";
import { UserCgn, UserCgnModel } from "../models/user_cgn";
import { UserCgnModel } from "../models/user_cgn";

type ResponseTypes =
| IResponseSuccessJson<Card>
Expand All @@ -39,17 +38,15 @@ export function GetCgnStatusHandler(
return async (_, fiscalCode) => {
return userCgnModel
.findLastVersionByModelId([fiscalCode])
.mapLeft(() =>
.mapLeft<IResponseErrorInternal | IResponseErrorNotFound>(() =>
ResponseErrorInternal("Error trying to retrieve user's CGN status")
)
.foldTaskEither<IResponseErrorInternal | IResponseErrorNotFound, UserCgn>(
fromLeft,
maybeUserCgn =>
fromEither(
fromOption(
ResponseErrorNotFound("Not Found", "User's CGN status not found")
)(maybeUserCgn)
)
.chain(maybeUserCgn =>
fromEither(
fromOption(
ResponseErrorNotFound("Not Found", "User's CGN status not found")
)(maybeUserCgn)
)
)
.fold<ResponseTypes>(identity, userCgn =>
ResponseSuccessJson(userCgn.card)
Expand Down
101 changes: 101 additions & 0 deletions GetEycaStatus/__tests__/handler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/* tslint:disable: no-any */

import * as date_fns from "date-fns";
import { some } from "fp-ts/lib/Option";
import { none } from "fp-ts/lib/Option";
import { fromLeft, taskEither } from "fp-ts/lib/TaskEither";
import { FiscalCode } from "italia-ts-commons/lib/strings";
import { NonEmptyString } from "italia-ts-commons/lib/strings";
import { cgnActivatedDates, now } from "../../__mocks__/mock";
import { StatusEnum as ActivatedStatusEnum } from "../../generated/definitions/CardActivated";
import {
CardPending,
StatusEnum as PendingStatusEnum
} from "../../generated/definitions/CardPending";
import { StatusEnum as RevokedStatusEnum } from "../../generated/definitions/CardRevoked";
import { EycaCardActivated } from "../../generated/definitions/EycaCardActivated";
import { EycaCardRevoked } from "../../generated/definitions/EycaCardRevoked";
import { UserEycaCard, UserEycaCardModel } from "../../models/user_eyca_card";
import { GetEycaStatusHandler } from "../handler";

const aFiscalCode = "RODFDS82S10H501T" as FiscalCode;
const aUserEycaCardNumber = "AN_ID" as NonEmptyString;

const aPendingEycaCard: CardPending = {
status: PendingStatusEnum.PENDING
};

const aUserEycaCard: UserEycaCard = {
card: aPendingEycaCard,
fiscalCode: aFiscalCode
};

const aRevokedEycaCard: EycaCardRevoked = {
...cgnActivatedDates,
card_number: aUserEycaCardNumber,
revocation_date: now,
revocation_reason: "A motivation" as NonEmptyString,
status: RevokedStatusEnum.REVOKED
};

const findLastVersionByModelIdMock = jest
.fn()
.mockImplementation(() => taskEither.of(some(aUserEycaCard)));
const userEycaCardModelMock = {
findLastVersionByModelId: findLastVersionByModelIdMock
};

const anActivatedEycaCard: EycaCardActivated = {
activation_date: now,
card_number: aUserEycaCardNumber,
expiration_date: date_fns.addDays(now, 10),
status: ActivatedStatusEnum.ACTIVATED
};

const successImpl = async (userEycaCard: UserEycaCard) => {
const handler = GetEycaStatusHandler(userEycaCardModelMock as any);
const response = await handler({} as any, aFiscalCode);
expect(response.kind).toBe("IResponseSuccessJson");
if (response.kind === "IResponseSuccessJson") {
expect(response.value).toEqual({
...userEycaCard.card
});
}
};
describe("GetEycaCardStatusHandler", () => {
it("should return an internal error when a query error occurs", async () => {
findLastVersionByModelIdMock.mockImplementationOnce(() =>
fromLeft(new Error("Query Error"))
);
const handler = GetEycaStatusHandler(userEycaCardModelMock as any);
const response = await handler({} as any, aFiscalCode);
expect(response.kind).toBe("IResponseErrorInternal");
});

it("should return not found if no userEycaCard is found", async () => {
findLastVersionByModelIdMock.mockImplementationOnce(() =>
taskEither.of(none)
);
const handler = GetEycaStatusHandler(userEycaCardModelMock as any);
const response = await handler({} as any, aFiscalCode);
expect(response.kind).toBe("IResponseErrorNotFound");
});

it("should return success if a pending userEycaCard is found", async () => {
await successImpl(aUserEycaCard);
});

it("should return success if a revoked userEycaCard is found", async () => {
findLastVersionByModelIdMock.mockImplementationOnce(() =>
taskEither.of(some({ ...aUserEycaCard, card: aRevokedEycaCard }))
);
await successImpl({ ...aUserEycaCard, card: aRevokedEycaCard });
});

it("should return success if an activated userEycaCard is found", async () => {
findLastVersionByModelIdMock.mockImplementationOnce(() =>
taskEither.of(some({ ...aUserEycaCard, card: anActivatedEycaCard }))
);
await successImpl({ ...aUserEycaCard, card: anActivatedEycaCard });
});
});
20 changes: 20 additions & 0 deletions GetEycaStatus/function.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"route": "api/v1/cgn/eyca/status/{fiscalcode}",
"methods": [
"get"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
}
],
"scriptFile": "../dist/GetEycaStatus/index.js"
}
75 changes: 75 additions & 0 deletions GetEycaStatus/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as express from "express";

import { Context } from "@azure/functions";
import { fromOption } from "fp-ts/lib/Either";
import { identity } from "fp-ts/lib/function";
import { fromEither } from "fp-ts/lib/TaskEither";
import { ContextMiddleware } from "io-functions-commons/dist/src/utils/middlewares/context_middleware";
import { RequiredParamMiddleware } from "io-functions-commons/dist/src/utils/middlewares/required_param";
import {
withRequestMiddlewares,
wrapRequestHandler
} from "io-functions-commons/dist/src/utils/request_middleware";
import {
IResponseErrorInternal,
IResponseErrorNotFound,
IResponseSuccessJson,
ResponseErrorInternal,
ResponseErrorNotFound,
ResponseSuccessJson
} from "italia-ts-commons/lib/responses";
import { FiscalCode } from "italia-ts-commons/lib/strings";

import { EycaCard } from "../generated/definitions/EycaCard";
import { UserEycaCardModel } from "../models/user_eyca_card";

type ResponseTypes =
| IResponseSuccessJson<EycaCard>
| IResponseErrorNotFound
| IResponseErrorInternal;

type IGetEycaStatusHandler = (
context: Context,
fiscalCode: FiscalCode
) => Promise<ResponseTypes>;

export function GetEycaStatusHandler(
userEycaCardModel: UserEycaCardModel
): IGetEycaStatusHandler {
return async (_, fiscalCode) => {
return userEycaCardModel
.findLastVersionByModelId([fiscalCode])
.mapLeft<IResponseErrorInternal | IResponseErrorNotFound>(() =>
ResponseErrorInternal(
"Error trying to retrieve user's EYCA Card status"
)
)
.chain(maybeUserEycaCard =>
fromEither(
fromOption(
ResponseErrorNotFound(
"Not Found",
"User's EYCA Card status not found"
)
)(maybeUserEycaCard)
)
)
.fold<ResponseTypes>(identity, userEycaCard =>
ResponseSuccessJson(userEycaCard.card)
)
.run();
};
}

export function GetEycaStatus(
userEycaCardModel: UserEycaCardModel
): express.RequestHandler {
const handler = GetEycaStatusHandler(userEycaCardModel);

const middlewaresWrap = withRequestMiddlewares(
ContextMiddleware(),
RequiredParamMiddleware("fiscalcode", FiscalCode)
);

return wrapRequestHandler(middlewaresWrap(handler));
}
56 changes: 56 additions & 0 deletions GetEycaStatus/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as express from "express";
import * as winston from "winston";

import { Context } from "@azure/functions";
import { secureExpressApp } from "io-functions-commons/dist/src/utils/express";
import { AzureContextTransport } from "io-functions-commons/dist/src/utils/logging";
import { setAppContext } from "io-functions-commons/dist/src/utils/middlewares/context_middleware";
import createAzureFunctionHandler from "io-functions-express/dist/src/createAzureFunctionsHandler";

import {
USER_EYCA_CARD_COLLECTION_NAME,
UserEycaCardModel
} from "../models/user_eyca_card";
import { getConfigOrThrow } from "../utils/config";
import { cosmosdbClient } from "../utils/cosmosdb";
import { GetEycaStatus } from "./handler";

//
// CosmosDB initialization
//

const config = getConfigOrThrow();

const userEycaCardsContainer = cosmosdbClient
.database(config.COSMOSDB_CGN_DATABASE_NAME)
.container(USER_EYCA_CARD_COLLECTION_NAME);

const userEycaCardModel = new UserEycaCardModel(userEycaCardsContainer);

// tslint:disable-next-line: no-let
let logger: Context["log"] | undefined;
const contextTransport = new AzureContextTransport(() => logger, {
level: "debug"
});
winston.add(contextTransport);

// Setup Express
const app = express();
secureExpressApp(app);

// Add express route
app.get(
"/api/v1/cgn/eyca/status/:fiscalcode",
GetEycaStatus(userEycaCardModel)
);

const azureFunctionHandler = createAzureFunctionHandler(app);

// Binds the express app to an Azure Function handler
function httpStart(context: Context): void {
logger = context.log;
setAppContext(app, context);
azureFunctionHandler(context);
}

export default httpStart;
27 changes: 27 additions & 0 deletions openapi/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,33 @@ paths:
description: Service unavailable.
schema:
$ref: "#/definitions/ProblemJson"

"/cgn/eyca/status/{fiscalcode}":
get:
summary: Get EYCA details Status
operationId: getEycaStatus
description: |
Get the EYCA status details by the provided fiscal code.
In case of success the response could be one of:
- CardPending
- EycaCardActivated
- EycaCardRevoked
- EycaCardExpired
parameters:
- $ref: "#/parameters/FiscalCode"
responses:
"200":
description: EYCA details.
schema:
$ref: "#/definitions/EycaCard"
"401":
description: Wrong or missing function key.
"404":
description: No EYCA found.
"500":
description: Service unavailable.
schema:
$ref: "#/definitions/ProblemJson"

"/cgn/{fiscalcode}/activation":
post:
Expand Down

0 comments on commit 70778e0

Please sign in to comment.