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

[ICC-201] Set ttl of message and message-status to 3 years for not registered users #230

Merged
merged 18 commits into from
Nov 9, 2022
Merged
88 changes: 86 additions & 2 deletions ProcessMessage/__tests__/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ import { ServiceId } from "@pagopa/io-functions-commons/dist/generated/definitio
import { RejectedMessageStatusValueEnum } from "@pagopa/io-functions-commons/dist/generated/definitions/RejectedMessageStatusValue";
import { RejectionReasonEnum } from "@pagopa/io-functions-commons/dist/generated/definitions/RejectionReason";

const TTL_FOR_USER_NOT_FOUND = 94670856 as NonNegativeInteger;

const createContext = (functionName: string = "funcname"): Context =>
(({
bindings: {},
Expand All @@ -86,9 +88,11 @@ const aBlobResult = {

const storeContentAsBlobMock = jest.fn(() => TE.of(O.some(aBlobResult)));
const upsertMessageMock = jest.fn<any, any>(() => TE.of(aRetrievedMessage));
const patchMessageMock = jest.fn((_, __) => TE.right(aRetrievedMessage));
const lMessageModel = ({
storeContentAsBlob: storeContentAsBlobMock,
upsert: upsertMessageMock
upsert: upsertMessageMock,
patch: patchMessageMock
} as unknown) as MessageModel;

const findServicePreferenceMock = jest.fn<any, any>(() =>
Expand All @@ -98,9 +102,11 @@ const lServicePreferencesModel = ({
find: findServicePreferenceMock
} as unknown) as ServicesPreferencesModel;

const updateTTLForAllVersionsMock = jest.fn(() => TE.right(1));
const lMessageStatusModel = ({
upsert: (...args) => TE.of({} /* anything */),
findLastVersionByModelId: (...args) => TE.right(O.none)
findLastVersionByModelId: (...args) => TE.right(O.none),
updateTTLForAllVersions: updateTTLForAllVersionsMock
} as unknown) as MS.MessageStatusModel;
const getMessageStatusUpdaterMock = jest.spyOn(MS, "getMessageStatusUpdater");

Expand Down Expand Up @@ -278,6 +284,8 @@ beforeEach(() => {
})
)
);
patchMessageMock.mockReturnValue(TE.right(aRetrievedMessage));
updateTTLForAllVersionsMock.mockReturnValue(TE.right(1));
});

afterEach(() => {
Expand Down Expand Up @@ -344,6 +352,7 @@ describe("getprocessMessageHandler", () => {
);

const processMessageHandler = getProcessMessageHandler({
TTL_FOR_USER_NOT_FOUND,
lActivation,
lProfileModel,
lMessageModel,
Expand Down Expand Up @@ -389,6 +398,8 @@ describe("getprocessMessageHandler", () => {
expect(activationFindLastVersionMock).toBeCalledTimes(
skipActivationMock ? 0 : 1
);
expect(patchMessageMock).not.toHaveBeenCalled();
expect(updateTTLForAllVersionsMock).not.toHaveBeenCalled();
}
);

Expand Down Expand Up @@ -439,6 +450,7 @@ describe("getprocessMessageHandler", () => {
);

const processMessageHandler = getProcessMessageHandler({
TTL_FOR_USER_NOT_FOUND,
lActivation,
lProfileModel,
lMessageModel,
Expand Down Expand Up @@ -478,6 +490,8 @@ describe("getprocessMessageHandler", () => {
payment_data: expectedMessagePaymentData
}
);
expect(patchMessageMock).not.toHaveBeenCalled();
expect(updateTTLForAllVersionsMock).not.toHaveBeenCalled();
}
);

Expand Down Expand Up @@ -531,6 +545,7 @@ describe("getprocessMessageHandler", () => {
TE.of(O.some(messageData))
);
const processMessageHandler = getProcessMessageHandler({
TTL_FOR_USER_NOT_FOUND,
lActivation,
lProfileModel,
lMessageModel,
Expand Down Expand Up @@ -577,6 +592,11 @@ describe("getprocessMessageHandler", () => {
expect(activationFindLastVersionMock).toBeCalledTimes(
skipActivationMock ? 0 : 1
);
//only in the first scenario the ttl should be setted
if (O.isSome(profileResult)) {
expect(patchMessageMock).not.toHaveBeenCalled();
expect(updateTTLForAllVersionsMock).not.toHaveBeenCalled();
}
}
);

Expand Down Expand Up @@ -628,6 +648,7 @@ describe("getprocessMessageHandler", () => {
TE.of(O.some(messageData))
);
const processMessageHandler = getProcessMessageHandler({
TTL_FOR_USER_NOT_FOUND,
lActivation,
lProfileModel,
lMessageModel,
Expand Down Expand Up @@ -659,6 +680,9 @@ describe("getprocessMessageHandler", () => {
expect(activationFindLastVersionMock).toBeCalledTimes(
skipActivationMock ? 0 : 1
);
// ensuring that ttl is never set in these scenarios
expect(patchMessageMock).not.toHaveBeenCalled();
expect(updateTTLForAllVersionsMock).not.toHaveBeenCalled();
}
);

Expand Down Expand Up @@ -709,6 +733,7 @@ describe("getprocessMessageHandler", () => {
TE.of(O.some(messageData))
);
const processMessageHandler = getProcessMessageHandler({
TTL_FOR_USER_NOT_FOUND,
lActivation,
lProfileModel,
lMessageModel,
Expand Down Expand Up @@ -743,6 +768,65 @@ describe("getprocessMessageHandler", () => {
skipPreferenceMock ? 0 : 1
);
expect(activationFindLastVersionMock).toBeCalledTimes(0);
expect(patchMessageMock).not.toHaveBeenCalled();
expect(updateTTLForAllVersionsMock).not.toHaveBeenCalled();
}
);

it("it should fail if profile is not found, and the ttl must be defined", async () => {
gquadrati marked this conversation as resolved.
Show resolved Hide resolved
findLastVersionByModelIdMock.mockImplementationOnce(() => {
return TE.of(O.none);
});
mockRetrieveProcessingMessageData.mockImplementationOnce(() =>
TE.of(O.some(aCommonMessageData))
);
const processMessageHandler = getProcessMessageHandler({
TTL_FOR_USER_NOT_FOUND,
lActivation,
lProfileModel,
lMessageModel,
lBlobService: {} as any,
lServicePreferencesModel,
lMessageStatusModel,
optOutEmailSwitchDate: aPastOptOutEmailSwitchDate,
pendingActivationGracePeriod: DEFAULT_PENDING_ACTIVATION_GRACE_PERIOD_SECONDS as Second,
isOptInEmailEnabled: false,
telemetryClient: mockTelemetryClient,
retrieveProcessingMessageData: mockRetrieveProcessingMessageData
});

const context = createContext();

await processMessageHandler(context, JSON.stringify(aCreatedMessageEvent));

const result = context.bindings.processedMessage;

expect(result).toBe(undefined);

expect(getMessageStatusUpdaterMock).toHaveBeenCalledTimes(1);
const messageStatusUpdaterMock = getMessageStatusUpdaterMock.mock.results[0]
.value as jest.Mock;
expect(messageStatusUpdaterMock).toHaveBeenCalledTimes(1);
const messageStatusUpdaterParam = messageStatusUpdaterMock.mock.calls[0][0];

expect(messageStatusUpdaterParam).toEqual({
status: RejectedMessageStatusValueEnum.REJECTED,
rejection_reason: RejectionReasonEnum.USER_NOT_FOUND
});

// check if models are being used only when expected
expect(findLastVersionByModelIdMock).toBeCalledTimes(1);
expect(patchMessageMock).toBeCalledTimes(1);
expect(patchMessageMock).toHaveBeenCalledWith(
["A_MESSAGE_ID", "AAABBB01C02D345D"],
{ ttl: 94670856 }
);
expect(updateTTLForAllVersionsMock).toBeCalledTimes(1);
expect(updateTTLForAllVersionsMock).toHaveBeenCalledWith(
["A_MESSAGE_ID"],
94670856
);
expect(findServicePreferenceMock).toBeCalledTimes(0);
expect(activationFindLastVersionMock).toBeCalledTimes(0);
});
});
61 changes: 60 additions & 1 deletion ProcessMessage/handler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable max-lines-per-function */

import { Context } from "@azure/functions";
import * as t from "io-ts";
import { BlockedInboxOrChannelEnum } from "@pagopa/io-functions-commons/dist/generated/definitions/BlockedInboxOrChannel";
import { EUCovidCert } from "@pagopa/io-functions-commons/dist/generated/definitions/EUCovidCert";
import { FiscalCode } from "@pagopa/io-functions-commons/dist/generated/definitions/FiscalCode";
Expand All @@ -12,7 +13,10 @@ import {
ServicesPreferencesModeEnum
} from "@pagopa/io-functions-commons/dist/generated/definitions/ServicesPreferencesMode";
import { ActivationModel } from "@pagopa/io-functions-commons/dist/src/models/activation";
import { MessageModel } from "@pagopa/io-functions-commons/dist/src/models/message";
import {
MessageModel,
MessageWithoutContent
} from "@pagopa/io-functions-commons/dist/src/models/message";
import {
getMessageStatusUpdater,
MessageStatusModel
Expand Down Expand Up @@ -347,7 +351,12 @@ const createMessageOrThrow = async (
}
};

// ttl can only be a positive integer or -1
const ttlType = t.union([NonNegativeInteger, t.literal(-1)]);
type TtlType = t.TypeOf<typeof ttlType>;

export interface IProcessMessageHandlerInput {
readonly TTL_FOR_USER_NOT_FOUND: TtlType;
readonly lActivation: ActivationModel;
readonly lProfileModel: ProfileModel;
readonly lMessageModel: MessageModel;
Expand All @@ -367,6 +376,7 @@ type Handler = (c: Context, i: unknown) => Promise<void>;
* Returns a function for handling ProcessMessage
*/
export const getProcessMessageHandler = ({
TTL_FOR_USER_NOT_FOUND,
lActivation,
lProfileModel,
lMessageModel,
Expand Down Expand Up @@ -422,6 +432,55 @@ export const getProcessMessageHandler = ({
rejection_reason: RejectionReasonEnum.USER_NOT_FOUND,
status: RejectedMessageStatusValueEnum.REJECTED
}),
TE.chain(() =>
pipe(
// setting TTL to 3 years for message-status entries
lMessageStatusModel.updateTTLForAllVersions(
[newMessageWithoutContent.id],
TTL_FOR_USER_NOT_FOUND
),
TE.mapLeft((error: CosmosErrors) => {
telemetryClient.trackEvent({
name: "api.messages.create.failstatusttlset",
gquadrati marked this conversation as resolved.
Show resolved Hide resolved
properties: {
error: `Something went wrong trying to update the ttl for the message status | ${error.kind}`,
gquadrati marked this conversation as resolved.
Show resolved Hide resolved
fiscalCode: toHash(newMessageWithoutContent.fiscalCode),
messageId: newMessageWithoutContent.id,
senderId: newMessageWithoutContent.senderServiceId
},
tagOverrides: { samplingEnabled: "false" }
});
return error;
})
)
),
TE.chain(() =>
pipe(
// setting TTL to 3 years for message entry
lMessageModel.patch(
[
newMessageWithoutContent.id,
newMessageWithoutContent.fiscalCode
],
{ ttl: TTL_FOR_USER_NOT_FOUND } as Partial<
MessageWithoutContent
>
gquadrati marked this conversation as resolved.
Show resolved Hide resolved
),
TE.mapLeft((error: CosmosErrors) => {
telemetryClient.trackEvent({
name: "api.messages.create.failttlset",
gquadrati marked this conversation as resolved.
Show resolved Hide resolved
properties: {
error: `Something went wrong trying to update the ttl for the message | ${error.kind}`,
gquadrati marked this conversation as resolved.
Show resolved Hide resolved
fiscalCode: toHash(newMessageWithoutContent.fiscalCode),
messageId: newMessageWithoutContent.id,
senderId: newMessageWithoutContent.senderServiceId
},
tagOverrides: { samplingEnabled: "false" }
});
return error;
})
)
),
TE.getOrElse(e => {
gquadrati marked this conversation as resolved.
Show resolved Hide resolved
context.log.error(
`${logPrefix}|PROFILE_NOT_FOUND|UPSERT_STATUS=REJECTED|ERROR=${JSON.stringify(
Expand Down
1 change: 1 addition & 0 deletions ProcessMessage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const retrieveProcessingMessageData = makeRetrieveExpandedDataFromBlob(
);

const activityFunctionHandler: AzureFunction = getProcessMessageHandler({
TTL_FOR_USER_NOT_FOUND: config.TTL_FOR_USER_NOT_FOUND,
isOptInEmailEnabled: config.FF_OPT_IN_EMAIL_ENABLED,
lActivation: activationModel,
lBlobService: blobServiceForMessageContent,
Expand Down
7 changes: 6 additions & 1 deletion utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import { BooleanFromString, withFallback } from "io-ts-types";
import { readableReport } from "@pagopa/ts-commons/lib/reporters";
import { NonEmptyString, Semver } from "@pagopa/ts-commons/lib/strings";
import { DateFromTimestamp } from "@pagopa/ts-commons/lib/dates";
import { NumberFromString } from "@pagopa/ts-commons/lib/numbers";
import {
NonNegativeInteger,
NumberFromString
} from "@pagopa/ts-commons/lib/numbers";
import { flow, pipe } from "fp-ts/lib/function";
import { CommaSeparatedListOf } from "./comma-separated-list";

Expand Down Expand Up @@ -84,6 +87,8 @@ export const IConfig = t.intersection([
// eslint-disable-next-line sort-keys
MIN_APP_VERSION_WITH_READ_AUTH: Semver,

TTL_FOR_USER_NOT_FOUND: t.union([NonNegativeInteger, t.literal(-1)]),

isProduction: t.boolean
}),
MessageContentStorageAccount,
Expand Down