Skip to content

Commit

Permalink
[#IP-292] Add override on isEmailEnabled value for old profiles (#137)
Browse files Browse the repository at this point in the history
  • Loading branch information
AleDore authored Jul 5, 2021
1 parent 225c5e4 commit 8b2d911
Show file tree
Hide file tree
Showing 11 changed files with 436 additions and 53 deletions.
2 changes: 1 addition & 1 deletion CreateNotificationActivity/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export const getCreateNotificationActivityHandler = (

// whether email notifications are enabled in this user profile - this is
// true by default, it's false only for users that have isEmailEnabled = false
// in their profile. We assume it's true when not defined in user's profile.
// in their profile.
const isEmailEnabledInProfile = profile.isEmailEnabled !== false;

// Check if the email in the user profile is validated.
Expand Down
2 changes: 1 addition & 1 deletion EmailNotificationActivity/__tests__/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { MessageSubject } from "@pagopa/io-functions-commons/dist/generated/defi
import { TimeToLiveSeconds } from "@pagopa/io-functions-commons/dist/generated/definitions/TimeToLiveSeconds";

import { NotificationChannelEnum } from "@pagopa/io-functions-commons/dist/generated/definitions/NotificationChannel";
import * as mail from "@pagopa/io-functions-commons/dist/src/mailer";
import * as mail from "@pagopa/io-functions-commons/dist/src/mailer/transports";
import { CreatedMessageEventSenderMetadata } from "@pagopa/io-functions-commons/dist/src/models/created_message_sender_metadata";
import {
NewNotification,
Expand Down
10 changes: 4 additions & 6 deletions GetSubscriptionsFeed/__tests__/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,16 @@

import { TableService } from "azure-storage";
import * as dateFmt from "date-fns";
import * as endOfTomorrow from "date-fns/end_of_tomorrow";
import * as startOfYesterday from "date-fns/start_of_yesterday";
import { FiscalCodeHash } from "../../generated/definitions/FiscalCodeHash";
import { GetSubscriptionsFeedHandler } from "../handler";

import { anIncompleteService, aValidService } from "../../__mocks__/mocks";

const tomorrow = endOfTomorrow();
const tomorrow = dateFmt.endOfTomorrow();

const yesterday = startOfYesterday();
const yesterday = dateFmt.startOfYesterday();

const yesterdayUTC = dateFmt.format(yesterday, "YYYY-MM-DD");
const yesterdayUTC = dateFmt.format(yesterday, "yyyy-MM-dd");

const userAttrs = {
email: "[email protected]",
Expand Down Expand Up @@ -82,7 +80,7 @@ describe("GetSubscriptionsFeedHandler", () => {
{} as any,
{} as any,
userAttrs as any,
dateFmt.format(tomorrow, "YYYY-MM-DD")
dateFmt.format(tomorrow, "yyyy-MM-dd")
);
expect(result.kind).toBe("IResponseErrorNotFound");
});
Expand Down
263 changes: 263 additions & 0 deletions StoreMessageContentActivity/__tests__/handler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { BlockedInboxOrChannelEnum } from "@pagopa/io-functions-commons/dist/generated/definitions/BlockedInboxOrChannel";
import { ServiceId } from "@pagopa/io-functions-commons/dist/generated/definitions/ServiceId";
import { CreatedMessageEvent } from "@pagopa/io-functions-commons/dist/src/models/created_message_event";
import { UTCISODateFromString } from "@pagopa/ts-commons/lib/dates";
import { NonNegativeNumber } from "@pagopa/ts-commons/lib/numbers";
import { fromLeft } from "fp-ts/lib/IOEither";
import { none, some } from "fp-ts/lib/Option";
import { taskEither } from "fp-ts/lib/TaskEither";
import {
aCreatedMessageEventSenderMetadata,
aMessageContent,
aNewMessageWithoutContent,
aRetrievedMessage,
aRetrievedProfile
} from "../../__mocks__/mocks";
import { getStoreMessageContentActivityHandler } from "../handler";

const mockContext = {
// eslint-disable no-console
log: {
error: console.error,
info: console.log,
verbose: console.log,
warn: console.warn
}
} as any;

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

const aBlobResult = {
name: "ABlobName"
};

const storeContentAsBlobMock = jest
.fn()
.mockImplementation(() => taskEither.of(some(aBlobResult)));
const upsertMessageMock = jest
.fn()
.mockImplementation(() => taskEither.of(aRetrievedMessage));
const messageModelMock = {
storeContentAsBlob: jest.fn(storeContentAsBlobMock),
upsert: jest.fn(upsertMessageMock)
};

const anOptOutEmailSwitchDate = UTCISODateFromString.decode(
"2021-07-08T23:59:59Z"
).getOrElseL(() => fail("wrong date value"));

const aPastOptOutEmailSwitchDate = UTCISODateFromString.decode(
"2000-07-08T23:59:59Z"
).getOrElseL(() => fail("wrong date value"));

const aCreatedMessageEvent: CreatedMessageEvent = {
content: aMessageContent,
message: aNewMessageWithoutContent,
senderMetadata: aCreatedMessageEventSenderMetadata,
serviceVersion: 1 as NonNegativeNumber
};

const aRetrievedProfileWithAValidTimestamp = {
...aRetrievedProfile,
_ts: 1625172947000
};
describe("getStoreMessageContentActivityHandler", () => {
it("should respond success with a retrieved profile with isEmailEnabled to false", async () => {
const storeMessageContentActivityHandler = getStoreMessageContentActivityHandler(
profileModelMock as any,
messageModelMock as any,
{} as any,
// limit date is after profile timestamp
anOptOutEmailSwitchDate
);

const result = await storeMessageContentActivityHandler(
mockContext,
aCreatedMessageEvent
);

expect(result.kind).toBe("SUCCESS");
if (result.kind === "SUCCESS") {
expect(result.blockedInboxOrChannels).toEqual([]);
expect(result.profile).toEqual({
...aRetrievedProfile,
isEmailEnabled: false
});
}
});

it("should respond success with a retrieved profile mantaining its original isEmailEnabled property", async () => {
findLastVersionByModelIdMock.mockImplementationOnce(() =>
taskEither.of(some(aRetrievedProfileWithAValidTimestamp))
);
const storeMessageContentActivityHandler = getStoreMessageContentActivityHandler(
profileModelMock as any,
messageModelMock as any,
{} as any,
// limit date is before profile timestamp
aPastOptOutEmailSwitchDate
);

const result = await storeMessageContentActivityHandler(
mockContext,
aCreatedMessageEvent
);

expect(result.kind).toBe("SUCCESS");
if (result.kind === "SUCCESS") {
expect(result.blockedInboxOrChannels).toEqual([]);
expect(result.profile).toEqual(aRetrievedProfileWithAValidTimestamp);
}
});

it("should fail if activity input cannot be decoded", async () => {
const storeMessageContentActivityHandler = getStoreMessageContentActivityHandler(
profileModelMock as any,
messageModelMock as any,
{} as any,
aPastOptOutEmailSwitchDate
);

const result = await storeMessageContentActivityHandler(
mockContext,
{} as any
);

expect(result.kind).toBe("FAILURE");
if (result.kind === "FAILURE") {
expect(result.reason).toEqual("BAD_DATA");
}
});

it("should throw an Error if there is an error while fetching profile", async () => {
findLastVersionByModelIdMock.mockImplementationOnce(() =>
fromLeft("Profile fetch error")
);
const storeMessageContentActivityHandler = getStoreMessageContentActivityHandler(
profileModelMock as any,
messageModelMock as any,
{} as any,
aPastOptOutEmailSwitchDate
);

await expect(
storeMessageContentActivityHandler(mockContext, aCreatedMessageEvent)
).rejects.toThrow();
});

it("should return a failure if no profile was found", async () => {
findLastVersionByModelIdMock.mockImplementationOnce(() =>
taskEither.of(none)
);
const storeMessageContentActivityHandler = getStoreMessageContentActivityHandler(
profileModelMock as any,
messageModelMock as any,
{} as any,
aPastOptOutEmailSwitchDate
);

const result = await storeMessageContentActivityHandler(
mockContext,
aCreatedMessageEvent
);

expect(result.kind).toBe("FAILURE");
if (result.kind === "FAILURE") {
expect(result.reason).toEqual("PROFILE_NOT_FOUND");
}
});

it("should return a failure if inbox is not enabled", async () => {
findLastVersionByModelIdMock.mockImplementationOnce(() =>
taskEither.of(some({ ...aRetrievedProfile, isInboxEnabled: false }))
);
const storeMessageContentActivityHandler = getStoreMessageContentActivityHandler(
profileModelMock as any,
messageModelMock as any,
{} as any,
aPastOptOutEmailSwitchDate
);

const result = await storeMessageContentActivityHandler(
mockContext,
aCreatedMessageEvent
);

expect(result.kind).toBe("FAILURE");
if (result.kind === "FAILURE") {
expect(result.reason).toEqual("MASTER_INBOX_DISABLED");
}
});

it("should return a failure if message sender is blocked", async () => {
findLastVersionByModelIdMock.mockImplementationOnce(() =>
taskEither.of(
some({
...aRetrievedProfile,
blockedInboxOrChannels: { myService: [BlockedInboxOrChannelEnum.INBOX] }
})
)
);
const storeMessageContentActivityHandler = getStoreMessageContentActivityHandler(
profileModelMock as any,
messageModelMock as any,
{} as any,
aPastOptOutEmailSwitchDate
);

const result = await storeMessageContentActivityHandler(
mockContext,
{
...aCreatedMessageEvent,
message: {
...aNewMessageWithoutContent,
senderServiceId: "myService" as ServiceId
}
}
);

expect(result.kind).toBe("FAILURE");
if (result.kind === "FAILURE") {
expect(result.reason).toEqual("SENDER_BLOCKED");
}
});

it("should throw an Error if message store operation fails", async () => {
storeContentAsBlobMock.mockImplementationOnce(() =>
fromLeft(new Error("Error while storing message content"))
);
const storeMessageContentActivityHandler = getStoreMessageContentActivityHandler(
profileModelMock as any,
messageModelMock as any,
{} as any,
aPastOptOutEmailSwitchDate
);

await expect(
storeMessageContentActivityHandler(mockContext, aCreatedMessageEvent)
).rejects.toThrow();
});

it("should throw an Error if message upsert fails", async () => {
upsertMessageMock.mockImplementationOnce(() =>
fromLeft(new Error("Error while upserting message"))
);
const storeMessageContentActivityHandler = getStoreMessageContentActivityHandler(
profileModelMock as any,
messageModelMock as any,
{} as any,
aPastOptOutEmailSwitchDate
);

await expect(
storeMessageContentActivityHandler(mockContext, aCreatedMessageEvent)
).rejects.toThrow();
});
});
14 changes: 12 additions & 2 deletions StoreMessageContentActivity/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { BlobService } from "azure-storage";
import { isLeft } from "fp-ts/lib/Either";
import { fromNullable, isNone } from "fp-ts/lib/Option";
import { readableReport } from "italia-ts-commons/lib/reporters";
import { isBefore } from "date-fns";
import { UTCISODateFromString } from "@pagopa/ts-commons/lib/dates";

export const SuccessfulStoreMessageContentActivityResult = t.interface({
blockedInboxOrChannels: t.readonlyArray(BlockedInboxOrChannel),
Expand Down Expand Up @@ -57,7 +59,8 @@ export type StoreMessageContentActivityResult = t.TypeOf<
export const getStoreMessageContentActivityHandler = (
lProfileModel: ProfileModel,
lMessageModel: MessageModel,
lBlobService: BlobService
lBlobService: BlobService,
optOutEmailSwitchDate: UTCISODateFromString
) => async (
context: Context,
input: unknown
Expand Down Expand Up @@ -181,6 +184,13 @@ export const getStoreMessageContentActivityHandler = (
// since a Set can't be serialized to JSON
blockedInboxOrChannels: Array.from(blockedInboxOrChannels),
kind: "SUCCESS",
profile
profile: {
...profile,
// if profile's timestamp is before email opt out switch limit date we must force isEmailEnabled to false
// eslint-disable-next-line no-underscore-dangle
isEmailEnabled: isBefore(profile._ts, optOutEmailSwitchDate)
? false
: profile.isEmailEnabled
}
};
};
3 changes: 2 additions & 1 deletion StoreMessageContentActivity/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ const blobService = createBlobService(config.QueueStorageConnection);
const activityFunctionHandler: AzureFunction = getStoreMessageContentActivityHandler(
profileModel,
messageModel,
blobService
blobService,
config.OPT_OUT_EMAIL_SWITCH_DATE
);

export default activityFunctionHandler;
Loading

0 comments on commit 8b2d911

Please sign in to comment.