Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds GetService function #1

Merged
merged 1 commit into from
Aug 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions GetService/__tests__/handler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// tslint:disable:no-any

import { none, some } from "fp-ts/lib/Option";

import { left, right } from "fp-ts/lib/Either";
import { NonNegativeNumber } from "italia-ts-commons/lib/numbers";
import {
NonEmptyString,
OrganizationFiscalCode
} from "italia-ts-commons/lib/strings";

import {
NewService,
RetrievedService,
Service,
toAuthorizedCIDRs,
toAuthorizedRecipients
} from "io-functions-commons/dist/src/models/service";

import { MaxAllowedPaymentAmount } from "io-functions-commons/dist/generated/definitions/MaxAllowedPaymentAmount";
import { ServicePublic } from "io-functions-commons/dist/generated/definitions/ServicePublic";

import { GetServiceHandler } from "../handler";

afterEach(() => {
jest.resetAllMocks();
jest.restoreAllMocks();
});

const anOrganizationFiscalCode = "01234567890" as OrganizationFiscalCode;

const aServicePayload: ServicePublic = {
department_name: "MyDeptName" as NonEmptyString,
organization_fiscal_code: anOrganizationFiscalCode,
organization_name: "MyOrgName" as NonEmptyString,
service_id: "MySubscriptionId" as NonEmptyString,
service_name: "MyServiceName" as NonEmptyString,
version: 1
};

const aService: Service = {
authorizedCIDRs: toAuthorizedCIDRs([]),
authorizedRecipients: toAuthorizedRecipients([]),
departmentName: "MyDeptName" as NonEmptyString,
isVisible: true,
maxAllowedPaymentAmount: 0 as MaxAllowedPaymentAmount,
organizationFiscalCode: anOrganizationFiscalCode,
organizationName: "MyOrgName" as NonEmptyString,
serviceId: "MySubscriptionId" as NonEmptyString,
serviceName: "MyServiceName" as NonEmptyString
};

const aNewService: NewService = {
...aService,
id: "123" as NonEmptyString,
kind: "INewService",
version: 1 as NonNegativeNumber
};

const aRetrievedService: RetrievedService = {
...aNewService,
_self: "123",
_ts: 123,
kind: "IRetrievedService"
};

const aSeralizedService: ServicePublic = {
...aServicePayload,
version: 1 as NonNegativeNumber
};

describe("GetServiceHandler", () => {
it("should get an existing service", async () => {
const serviceModelMock = {
findOneByServiceId: jest.fn(() => {
return Promise.resolve(right(some(aRetrievedService)));
})
};
const aServiceId = "1" as NonEmptyString;
const getServiceHandler = GetServiceHandler(serviceModelMock as any);
const response = await getServiceHandler(aServiceId);
expect(serviceModelMock.findOneByServiceId).toHaveBeenCalledWith(
aServiceId
);
expect(response.kind).toBe("IResponseSuccessJson");
if (response.kind === "IResponseSuccessJson") {
expect(response.value).toEqual(aSeralizedService);
}
});
it("should fail on errors during get", async () => {
const serviceModelMock = {
findOneByServiceId: jest.fn(() => {
return Promise.resolve(left(none));
})
};
const aServiceId = "1" as NonEmptyString;
const getServiceHandler = GetServiceHandler(serviceModelMock as any);
const response = await getServiceHandler(aServiceId);
expect(serviceModelMock.findOneByServiceId).toHaveBeenCalledWith(
aServiceId
);
expect(response.kind).toBe("IResponseErrorQuery");
});
it("should return not found if the service does not exist", async () => {
const serviceModelMock = {
findOneByServiceId: jest.fn(() => {
return Promise.resolve(right(none));
})
};
const aServiceId = "1" as NonEmptyString;
const getServiceHandler = GetServiceHandler(serviceModelMock as any);
const response = await getServiceHandler(aServiceId);
expect(serviceModelMock.findOneByServiceId).toHaveBeenCalledWith(
aServiceId
);
expect(response.kind).toBe("IResponseErrorNotFound");
});
});
21 changes: 21 additions & 0 deletions GetService/function.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"route": "v1/services/{serviceid}",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
}
],
"scriptFile": "../dist/GetService/index.js"
}
96 changes: 96 additions & 0 deletions GetService/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Implements the Public API handlers for the Services resource.
*/

import * as express from "express";

import {
IResponseErrorNotFound,
IResponseSuccessJson,
ResponseErrorNotFound,
ResponseSuccessJson
} from "italia-ts-commons/lib/responses";

import { NonEmptyString } from "italia-ts-commons/lib/strings";

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 {
IResponseErrorQuery,
ResponseErrorQuery
} from "io-functions-commons/dist/src/utils/response";

import {
RetrievedService,
ServiceModel
} from "io-functions-commons/dist/src/models/service";

import { ServiceId } from "io-functions-commons/dist/generated/definitions/ServiceId";
import { ServicePublic } from "io-functions-commons/dist/generated/definitions/ServicePublic";

type IGetServiceHandlerRet =
| IResponseSuccessJson<ServicePublic>
| IResponseErrorNotFound
| IResponseErrorQuery;

type IGetServiceHandler = (
serviceId: ServiceId
) => Promise<IGetServiceHandlerRet>;

/**
* Converts a retrieved service to a service that can be shared via API
*/
function retrievedServiceToPublic(
retrievedService: RetrievedService
): ServicePublic {
return {
department_name: retrievedService.departmentName,
organization_fiscal_code: retrievedService.organizationFiscalCode,
organization_name: retrievedService.organizationName,
service_id: retrievedService.serviceId,
service_name: retrievedService.serviceName,
version: retrievedService.version
};
}

/**
* Extracts the serviceId value from the URL path parameter.
*/
const requiredServiceIdMiddleware = RequiredParamMiddleware(
"serviceid",
NonEmptyString
);

export function GetServiceHandler(
serviceModel: ServiceModel
): IGetServiceHandler {
return async serviceId =>
(await serviceModel.findOneByServiceId(serviceId)).fold<
IGetServiceHandlerRet
>(
error => ResponseErrorQuery("Error while retrieving the service", error),
maybeService =>
maybeService.foldL<
IResponseErrorNotFound | IResponseSuccessJson<ServicePublic>
>(
() =>
ResponseErrorNotFound(
"Service not found",
"The service you requested was not found in the system."
),
service => ResponseSuccessJson(retrievedServiceToPublic(service))
)
);
}

/**
* Wraps a GetService handler inside an Express request handler.
*/
export function GetService(serviceModel: ServiceModel): express.RequestHandler {
const handler = GetServiceHandler(serviceModel);
const middlewaresWrap = withRequestMiddlewares(requiredServiceIdMiddleware);
return wrapRequestHandler(middlewaresWrap(handler));
}
64 changes: 64 additions & 0 deletions GetService/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Context } from "@azure/functions";

import * as cors from "cors";
import * as express from "express";
import * as winston from "winston";

import { DocumentClient as DocumentDBClient } from "documentdb";

import {
SERVICE_COLLECTION_NAME,
ServiceModel
} from "io-functions-commons/dist/src/models/service";
import * as documentDbUtils from "io-functions-commons/dist/src/utils/documentdb";
import { getRequiredStringEnv } from "io-functions-commons/dist/src/utils/env";
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 { GetService } from "./handler";

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

// Set up CORS (free access to the API from browser clients)
app.use(cors());

const cosmosDbUri = getRequiredStringEnv("CUSTOMCONNSTR_COSMOSDB_URI");
const cosmosDbKey = getRequiredStringEnv("CUSTOMCONNSTR_COSMOSDB_KEY");
const cosmosDbName = getRequiredStringEnv("COSMOSDB_NAME");

const documentDbDatabaseUrl = documentDbUtils.getDatabaseUri(cosmosDbName);
const servicesCollectionUrl = documentDbUtils.getCollectionUri(
documentDbDatabaseUrl,
SERVICE_COLLECTION_NAME
);

const documentClient = new DocumentDBClient(cosmosDbUri, {
masterKey: cosmosDbKey
});

const serviceModel = new ServiceModel(documentClient, servicesCollectionUrl);

app.get("/api/v1/services/:serviceid", GetService(serviceModel));

const azureFunctionHandler = createAzureFunctionHandler(app);

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

// 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;