Skip to content

Commit

Permalink
[#IOCIT-105] added servicesPreferences to profile data download (#191)
Browse files Browse the repository at this point in the history
  • Loading branch information
arcogabbo authored Sep 12, 2022
1 parent cc254f0 commit 4c2125e
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 42 deletions.
51 changes: 45 additions & 6 deletions ExtractUserDataActivity/__tests__/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
aMessageView,
aProfile,
aRetrievedMessageStatus,
aRetrievedNotificationStatus
aRetrievedNotificationStatus,
aRetrievedServicePreferences
} from "../../__mocks__/mocks";

import {
Expand Down Expand Up @@ -45,6 +46,7 @@ import { AllUserData } from "../../utils/userData";
import { none } from "fp-ts/lib/Option";
import { pipe } from "fp-ts/lib/function";
import { MessageViewModel } from "@pagopa/io-functions-commons/dist/src/models/message_view";
import { ServicePreferencesDeletableModel } from "../../utils/extensions/models/service_preferences";

const anotherRetrievedNotification: RetrievedNotification = {
...aRetrievedNotification,
Expand Down Expand Up @@ -110,6 +112,38 @@ const messageModelMock = ({
getContentFromBlob: mockGetContentFromBlob
} as any) as MessageModel;

// ServicePreferences Model
const asyncIteratorOf = <T>(items: T[]): AsyncIterator<T[]> => {
const data = [...items];
return {
next: async () => {
const value = data.shift();
return {
done: typeof value === "undefined",
value: [value]
};
}
};
};

const mockDeleteServicePreferences = jest.fn<
ReturnType<InstanceType<typeof ServicePreferencesDeletableModel>["delete"]>,
Parameters<InstanceType<typeof ServicePreferencesDeletableModel>["delete"]>
>(() => TE.of("anything"));
const mockFindAllServPreferencesByFiscalCode = jest.fn<
ReturnType<
InstanceType<typeof ServicePreferencesDeletableModel>["findAllByFiscalCode"]
>,
Parameters<
InstanceType<typeof ServicePreferencesDeletableModel>["findAllByFiscalCode"]
>
>(() => asyncIteratorOf([E.right(aRetrievedServicePreferences)]));

const servicePreferencesModelMock = ({
delete: mockDeleteServicePreferences,
findAllByFiscalCode: mockFindAllServPreferencesByFiscalCode
} as unknown) as ServicePreferencesDeletableModel;

const iteratorGenMock = async function*(arr: any[]) {
for (let a of arr) yield a;
};
Expand Down Expand Up @@ -187,7 +221,8 @@ describe("createExtractUserDataActivityHandler", () => {
notificationStatusModel: notificationStatusModelMock,
profileModel: profileModelMock,
userDataBlobService: blobServiceMock,
userDataContainerName: aUserDataContainerName
userDataContainerName: aUserDataContainerName,
servicePreferencesModel: servicePreferencesModelMock
});
const input: ActivityInput = {
fiscalCode: aFiscalCode
Expand Down Expand Up @@ -225,7 +260,8 @@ describe("createExtractUserDataActivityHandler", () => {
notificationStatusModel: notificationStatusModelMock,
profileModel: profileModelMock,
userDataBlobService: blobServiceMock,
userDataContainerName: aUserDataContainerName
userDataContainerName: aUserDataContainerName,
servicePreferencesModel: servicePreferencesModelMock
});
const input: ActivityInput = {
fiscalCode: aFiscalCode
Expand Down Expand Up @@ -255,7 +291,8 @@ describe("createExtractUserDataActivityHandler", () => {
notificationStatusModel: notificationStatusModelMock,
profileModel: profileModelMock,
userDataBlobService: blobServiceMock,
userDataContainerName: aUserDataContainerName
userDataContainerName: aUserDataContainerName,
servicePreferencesModel: servicePreferencesModelMock
});
const input: ActivityInput = {
fiscalCode: aFiscalCode
Expand Down Expand Up @@ -299,7 +336,8 @@ describe("createExtractUserDataActivityHandler", () => {
notificationStatusModel: notificationStatusModelMock,
profileModel: profileModelMock,
userDataBlobService: blobServiceMock,
userDataContainerName: aUserDataContainerName
userDataContainerName: aUserDataContainerName,
servicePreferencesModel: servicePreferencesModelMock
});
const input: ActivityInput = {
fiscalCode: aFiscalCode
Expand All @@ -322,7 +360,8 @@ describe("createExtractUserDataActivityHandler", () => {
notificationStatusModel: notificationStatusModelMock,
profileModel: profileModelMock,
userDataBlobService: blobServiceMock,
userDataContainerName: aUserDataContainerName
userDataContainerName: aUserDataContainerName,
servicePreferencesModel: servicePreferencesModelMock
});
const input: ActivityInput = {
fiscalCode: aFiscalCode
Expand Down
83 changes: 57 additions & 26 deletions ExtractUserDataActivity/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { DeferredPromise } from "@pagopa/ts-commons/lib/promises";

import { sequenceS, sequenceT } from "fp-ts/lib/Apply";
import * as A from "fp-ts/lib/Array";
import * as ROA from "fp-ts/lib/ReadonlyArray";
import { flatten, rights } from "fp-ts/lib/Array";

import { Context } from "@azure/functions";
Expand Down Expand Up @@ -39,7 +40,10 @@ import { FiscalCode, NonEmptyString } from "@pagopa/ts-commons/lib/strings";
import * as E from "fp-ts/lib/Either";
import * as O from "fp-ts/lib/Option";
import * as TE from "fp-ts/lib/TaskEither";
import { asyncIteratorToArray } from "@pagopa/io-functions-commons/dist/src/utils/async";
import {
asyncIteratorToArray,
flattenAsyncIterator
} from "@pagopa/io-functions-commons/dist/src/utils/async";
import { toCosmosErrorResponse } from "@pagopa/io-functions-commons/dist/src/utils/cosmosdb_model";
import * as yaml from "yaml";
import { pipe, flow } from "fp-ts/lib/function";
Expand All @@ -48,6 +52,7 @@ import { getEncryptedZipStream } from "../utils/zip";
import { AllUserData, MessageContentWithId } from "../utils/userData";
import { generateStrongPassword, StrongPassword } from "../utils/random";
import { getMessageFromCosmosErrors } from "../utils/conversions";
import { ServicePreferencesDeletableModel } from "../utils/extensions/models/service_preferences";

export const ArchiveInfo = t.interface({
blobName: NonEmptyString,
Expand Down Expand Up @@ -125,8 +130,9 @@ const logPrefix = `ExtractUserDataActivity`;
* Converts a Promise<Either<L, R>> that can reject
* into a TaskEither<Error | L, R>
*/
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
const fromPromiseEither = <L, R>(promise: Promise<E.Either<L, R>>) =>
const fromPromiseEither = <L, R>(
promise: Promise<E.Either<L, R>>
): TE.TaskEither<L | Error, R> =>
pipe(
TE.tryCatch(() => promise, E.toError),
TE.chainW(TE.fromEither)
Expand Down Expand Up @@ -363,7 +369,8 @@ export const queryAllUserData = (
notificationStatusModel: NotificationStatusModel,
profileModel: ProfileModel,
messageContentBlobService: BlobService,
fiscalCode: FiscalCode
fiscalCode: FiscalCode,
servicePreferencesModel: ServicePreferencesDeletableModel
): TE.TaskEither<
ActivityResultUserNotFound | ActivityResultQueryFailure,
AllUserData
Expand Down Expand Up @@ -441,11 +448,28 @@ export const queryAllUserData = (
: TE.of(rights(results))
)
),
profile: TE.of(profile)
profile: TE.of(profile),
servicesPreferences: pipe(
servicePreferencesModel.findAllByFiscalCode(fiscalCode),
flattenAsyncIterator,
asyncIteratorToArray,
promise =>
TE.tryCatch(
() => promise,
() =>
ActivityResultQueryFailure.encode({
kind: "QUERY_FAILURE",
reason: "Error with the async operator"
})
),
// ROA.rights will return only the right values obtained from the database
// (left values represent malformed data inside the database)
TE.map(ROA.rights)
)
})
),
// step 2: queries notifications and message contents, which need message data to be queried first
TE.chain(({ profile, messages, messagesView }) =>
TE.chain(({ profile, messages, messagesView, servicesPreferences }) =>
sequenceS(TE.ApplicativePar)({
messageContents: getAllMessageContents(
messageContentBlobService,
Expand All @@ -459,31 +483,34 @@ export const queryAllUserData = (
notificationModel,
messages
),
profile: TE.of(profile)
profile: TE.of(profile),
servicesPreferences: TE.of(servicesPreferences)
})
),
// step 3: queries notifications statuses
TE.chain(
TE.bind("notificationStatuses", ({ notifications }) =>
findAllNotificationStatuses(notificationStatusModel, notifications)
),
TE.map(
({
profile,
notifications,
messages,
messagesView,
messageContents,
messageStatuses,
notifications
}) =>
sequenceS(TE.ApplicativePar)({
messageContents: TE.of(messageContents),
messageStatuses: TE.of(messageStatuses),
messages: TE.of(messages),
messagesView: TE.of(messagesView),
notificationStatuses: findAllNotificationStatuses(
notificationStatusModel,
notifications
),
notifications: TE.of(notifications),
profiles: TE.of([profile])
})
notificationStatuses,
servicesPreferences
}) => ({
messageContents,
messageStatuses,
messages,
messagesView,
notificationStatuses,
notifications,
profiles: [profile],
servicesPreferences
})
)
);

Expand Down Expand Up @@ -589,6 +616,7 @@ export interface IActivityHandlerInput {
readonly messageContentBlobService: BlobService;
readonly userDataBlobService: BlobService;
readonly userDataContainerName: NonEmptyString;
readonly servicePreferencesModel: ServicePreferencesDeletableModel;
}

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type, @typescript-eslint/no-explicit-any
Expand All @@ -611,7 +639,8 @@ export function createExtractUserDataActivityHandler({
profileModel,
messageContentBlobService,
userDataBlobService,
userDataContainerName
userDataContainerName,
servicePreferencesModel
}: IActivityHandlerInput): (
context: Context,
input: unknown
Expand All @@ -637,7 +666,8 @@ export function createExtractUserDataActivityHandler({
notificationStatusModel,
profileModel,
messageContentBlobService,
fiscalCode
fiscalCode,
servicePreferencesModel
)
),
TE.map(allUserData => {
Expand All @@ -655,8 +685,9 @@ export function createExtractUserDataActivityHandler({
messagesView: allUserData.messagesView.map(cleanData),
notificationStatuses: allUserData.messageStatuses.map(cleanData),
notifications,
profiles: allUserData.profiles.map(cleanData)
} as AllUserData;
profiles: allUserData.profiles.map(cleanData),
servicesPreferences: allUserData.servicesPreferences.map(cleanData)
};
}),
TE.chainW(allUserData =>
saveDataToBlob(
Expand Down
8 changes: 8 additions & 0 deletions ExtractUserDataActivity/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ import {
MessageViewModel,
MESSAGE_VIEW_COLLECTION_NAME
} from "@pagopa/io-functions-commons/dist/src/models/message_view";
import { SERVICE_PREFERENCES_COLLECTION_NAME } from "@pagopa/io-functions-commons/dist/src/models/service_preference";
import { cosmosdbClient } from "../utils/cosmosdb";
import { getConfigOrThrow } from "../utils/config";
import { ServicePreferencesDeletableModel } from "../utils/extensions/models/service_preferences";
import { createExtractUserDataActivityHandler } from "./handler";

const config = getConfigOrThrow();
Expand Down Expand Up @@ -58,6 +60,11 @@ const profileModel = new ProfileModel(
database.container(PROFILE_COLLECTION_NAME)
);

const servicePreferencesModel = new ServicePreferencesDeletableModel(
database.container(SERVICE_PREFERENCES_COLLECTION_NAME),
SERVICE_PREFERENCES_COLLECTION_NAME
);

const userDataBlobService = createBlobService(
config.UserDataArchiveStorageConnection
);
Expand All @@ -74,6 +81,7 @@ const activityFunctionHandler = createExtractUserDataActivityHandler({
notificationModel,
notificationStatusModel,
profileModel,
servicePreferencesModel,
userDataBlobService,
userDataContainerName
});
Expand Down
23 changes: 14 additions & 9 deletions __mocks__/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ import { ServicesPreferencesModeEnum } from "@pagopa/io-functions-commons/dist/g
import {
AccessReadMessageStatusEnum,
makeServicesPreferencesDocumentId,
RetrievedServicePreference
RetrievedServicePreference,
ServicePreference
} from "@pagopa/io-functions-commons/dist/src/models/service_preference";
import * as E from "fp-ts/lib/Either";
import { pipe } from "fp-ts/lib/function";
Expand Down Expand Up @@ -378,24 +379,28 @@ export const aArchiveInfo = pipe(

export const aServicePreferenceVersion = 0 as NonNegativeInteger;

export const aServicePreference: ServicePreference = {
fiscalCode: aFiscalCode,
serviceId: aServiceId,
settingsVersion: aServicePreferenceVersion,
isWebhookEnabled: true,
isEmailEnabled: true,
isInboxEnabled: true,
accessReadMessageStatus: AccessReadMessageStatusEnum.ALLOW
};

export const aRetrievedServicePreferences: RetrievedServicePreference = {
...{
_etag: "_etag",
_rid: "_rid",
_self: "_self",
_ts: 1
},
isEmailEnabled: true,
isInboxEnabled: true,
isWebhookEnabled: true,
settingsVersion: aServicePreferenceVersion,
fiscalCode: aFiscalCode,
serviceId: aServiceId,
...aServicePreference,
kind: "IRetrievedServicePreference",
id: makeServicesPreferencesDocumentId(
aFiscalCode,
aServiceId,
aServicePreferenceVersion
),
accessReadMessageStatus: AccessReadMessageStatusEnum.ALLOW
)
};
33 changes: 33 additions & 0 deletions utils/__tests__/userData.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { aServicePreference } from "../../__mocks__/mocks";
import { AllUserData } from "../userData";
import * as E from "fp-ts/lib/Either";

const mockedUserData: AllUserData = {
messageContents: [],
messageStatuses: [],
messagesView: [],
messages: [],
notifications: [],
notificationStatuses: [],
profiles: [],
servicesPreferences: [aServicePreference]
};

const mockedUserDataWithAdditionalProperty = {
...mockedUserData,
servicesPreferences: [{ ...aServicePreference, foo: 1, bar: "hello" }]
};

describe("servicePreference decoding", () => {
it("should remove additional properties", () => {
const result = AllUserData.decode(mockedUserDataWithAdditionalProperty);

expect(E.isRight(result)).toBeTruthy();
if (E.isRight(result)) {
expect(result.right).toMatchObject(mockedUserData);
expect(result.right.servicesPreferences[0]).toStrictEqual(
aServicePreference
);
}
});
});
Loading

0 comments on commit 4c2125e

Please sign in to comment.