Skip to content

Commit

Permalink
[#176068662] Enable check on EmailValidation Orchestrator (#132)
Browse files Browse the repository at this point in the history
* [#176068662] Enable check on orchestrators running

* [#176068662] Fold refactor

* [#176068662] Fix tests

* [#176068662] insert chain instead of foldTaskEither

* [#176068662] fix lint

* [#176068662] Remove error log

* [#176068662] Refactor over review

* [#176068662] Add date on orchestrator id

Co-authored-by: Danilo Spinelli <gunzip@users.noreply.github.com>
Co-authored-by: Emanuele De Cupis <ema.decu@gmail.com>
  • Loading branch information
3 people committed Dec 14, 2020
1 parent 704cb0d commit bba1cf3
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 41 deletions.
70 changes: 45 additions & 25 deletions StartEmailValidationProcess/__tests__/handler.test.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
/* tslint:disable: no-any */

import * as durableFunction from "durable-functions";
import { some } from "fp-ts/lib/Option";

import * as df from "durable-functions";

import { taskEither } from "fp-ts/lib/TaskEither";
import { context as contextMock } from "../../__mocks__/durable-functions";
import {
context as contextMock,
mockStartNew
} from "../../__mocks__/durable-functions";
import { aRetrievedProfile } from "../../__mocks__/mocks";
import { OrchestratorInput as EmailValidationProcessOrchestratorInput } from "../../EmailValidationProcessOrchestrator/handler";
import { StartEmailValidationProcessHandler } from "../handler";
import * as orchUtil from "../orchestrators";

beforeEach(() => {
(df.getClient as any).mockClear();
(df as any).mockStartNew.mockClear();
});
const getClientMock = {
startNew: mockStartNew
} as any;

const isOrchestratorRunningMock = jest.fn(() =>
taskEither.of({
isRunning: false
})
);
jest.spyOn(durableFunction, "getClient").mockImplementation(_ => getClientMock);
jest
.spyOn(orchUtil, "isOrchestratorRunning")
.mockImplementation(isOrchestratorRunningMock as any);
describe("StartEmailValidationProcessHandler", () => {
it("should start the orchestrator with the right input and return an acceppted response", async () => {
beforeEach(() => mockStartNew.mockClear());
it("should start the orchestrator with the right input and return an accepted response", async () => {
const profileModelMock = {
findLastVersionByModelId: jest.fn(() =>
taskEither.of(some({ ...aRetrievedProfile, isEmailValidated: false }))
)
};
mockStartNew.mockImplementationOnce(() => Promise.resolve("start"));

const handler = StartEmailValidationProcessHandler(profileModelMock as any);

Expand All @@ -30,21 +40,28 @@ describe("StartEmailValidationProcessHandler", () => {
aRetrievedProfile.fiscalCode
);

const emailValidationProcessOrchestratorInput = EmailValidationProcessOrchestratorInput.encode(
{
email: aRetrievedProfile.email,
fiscalCode: aRetrievedProfile.fiscalCode
}
);
expect(result.kind).toBe("IResponseSuccessAccepted");
});

expect(df.getClient).toHaveBeenCalledTimes(1);
it("should not start a new orchestrator if there is an already running orchestrator and return an accepted response", async () => {
const profileModelMock = {
findLastVersionByModelId: jest.fn(() =>
taskEither.of(some({ ...aRetrievedProfile, isEmailValidated: false }))
)
};

const dfClient = df.getClient(contextMock);
expect(dfClient.startNew).toHaveBeenCalledWith(
"EmailValidationProcessOrchestrator",
undefined,
emailValidationProcessOrchestratorInput
isOrchestratorRunningMock.mockImplementationOnce(() =>
taskEither.of({
isRunning: true
})
);
const handler = StartEmailValidationProcessHandler(profileModelMock as any);

const result = await handler(
contextMock as any,
aRetrievedProfile.fiscalCode
);
expect(mockStartNew).not.toHaveBeenCalled();

expect(result.kind).toBe("IResponseSuccessAccepted");
});
Expand All @@ -59,7 +76,10 @@ describe("StartEmailValidationProcessHandler", () => {
const handler = StartEmailValidationProcessHandler(profileModelMock as any);

await handler(contextMock as any, aRetrievedProfile.fiscalCode);

expect(df.getClient).not.toHaveBeenCalledTimes(1);
const result = await handler(
contextMock as any,
aRetrievedProfile.fiscalCode
);
expect(result.kind).toBe("IResponseErrorValidation");
});
});
60 changes: 44 additions & 16 deletions StartEmailValidationProcess/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ import * as express from "express";
import { Context } from "@azure/functions";
import * as df from "durable-functions";

import { isLeft } from "fp-ts/lib/Either";
import { isLeft, toError } from "fp-ts/lib/Either";
import { isNone } from "fp-ts/lib/Option";

import {
IResponseErrorInternal,
IResponseErrorNotFound,
IResponseErrorValidation,
IResponseSuccessAccepted,
IResponseSuccessJson,
ResponseErrorInternal,
ResponseErrorNotFound,
ResponseErrorValidation,
ResponseSuccessAccepted
Expand All @@ -36,22 +38,29 @@ import {
wrapRequestHandler
} from "io-functions-commons/dist/src/utils/request_middleware";

import { fromPredicate, taskEither, tryCatch } from "fp-ts/lib/TaskEither";
import { OrchestratorInput as EmailValidationProcessOrchestratorInput } from "../EmailValidationProcessOrchestrator/handler";
import {
isOrchestratorRunning,
makeStartEmailValidationProcessOrchestratorId
} from "./orchestrators";

/**
* Type of an StartEmailValidationProcess handler.
*/
type IStartEmailValidationProcessHandler = (
context: Context,
fiscalCode: FiscalCode
) => Promise<
type ReturnTypes =
// tslint:disable-next-line: max-union-size
| IResponseSuccessJson<{}>
| IResponseErrorValidation
| IResponseErrorQuery
| IResponseErrorNotFound
| IResponseSuccessAccepted
>;
| IResponseErrorInternal;

/**
* Type of an StartEmailValidationProcess handler.
*/
type IStartEmailValidationProcessHandler = (
context: Context,
fiscalCode: FiscalCode
) => Promise<ReturnTypes>;

export function StartEmailValidationProcessHandler(
profileModel: ProfileModel
Expand Down Expand Up @@ -99,13 +108,32 @@ export function StartEmailValidationProcessHandler(
);

const dfClient = df.getClient(context);
await dfClient.startNew(
"EmailValidationProcessOrchestrator",
undefined,
emailValidationProcessOrchestartorInput
);

return ResponseSuccessAccepted();
return taskEither
.of(makeStartEmailValidationProcessOrchestratorId(fiscalCode, email))
.chain(orchId =>
isOrchestratorRunning(dfClient, orchId)
.chain(
fromPredicate(_ => _.isRunning, () => new Error("Not Running"))
)
.foldTaskEither(
() =>
tryCatch(
() =>
dfClient.startNew(
"EmailValidationProcessOrchestrator",
orchId,
emailValidationProcessOrchestartorInput
),
toError
),
_ => taskEither.of(String(_.isRunning))
)
)
.fold<ReturnTypes>(
e => ResponseErrorInternal(String(e)),
() => ResponseSuccessAccepted()
)
.run();
};
}

Expand Down
50 changes: 50 additions & 0 deletions StartEmailValidationProcess/orchestrators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as crypto from "crypto";
import * as df from "durable-functions";
import { DurableOrchestrationClient } from "durable-functions/lib/src/durableorchestrationclient";

import * as dateFns from "date-fns";
import { toError } from "fp-ts/lib/Either";
import { TaskEither, tryCatch } from "fp-ts/lib/TaskEither";

import { FiscalCode } from "italia-ts-commons/lib/strings";
import { PromiseType } from "italia-ts-commons/lib/types";
import { EmailAddress } from "../generated/backend/EmailAddress";

const hashCreator = (value: string) =>
crypto
.createHash("sha256")
.update(value)
.digest("hex");

/**
* The identifier for EmailValidationProcessOrchestrator
* @param fiscalCode the id of the requesting user
* @param email the user's email
*/
export const makeStartEmailValidationProcessOrchestratorId = (
fiscalCode: FiscalCode,
email: EmailAddress,
creationDate: Date = new Date()
) =>
hashCreator(
`${dateFns.format(creationDate, "dd/MM/yyyy")}-${fiscalCode}-${email}`
);

/**
* Returns the status of the orchestrator augmented with an isRunning attribute
*/
export const isOrchestratorRunning = (
client: DurableOrchestrationClient,
orchestratorId: string
): TaskEither<
Error,
PromiseType<ReturnType<typeof client["getStatus"]>> & {
isRunning: boolean;
}
> =>
tryCatch(() => client.getStatus(orchestratorId), toError).map(status => ({
...status,
isRunning:
status.runtimeStatus === df.OrchestrationRuntimeStatus.Running ||
status.runtimeStatus === df.OrchestrationRuntimeStatus.Pending
}));
4 changes: 4 additions & 0 deletions openapi/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,10 @@ paths:
description: Not found
"429":
description: Too many requests
"500":
description: Server Error
schema:
$ref: "#/definitions/ProblemJson"
"/user-data-processing/{fiscal_code}":
post:
operationId: upsertUserDataProcessing
Expand Down

0 comments on commit bba1cf3

Please sign in to comment.