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

[#IOPID-1450] Enable strict mode on repo #231

Merged
merged 12 commits into from
Mar 19, 2024
7 changes: 4 additions & 3 deletions CreateDevelopmentProfile/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@ export function toExtendedProfile(profile: RetrievedProfile): ExtendedProfile {
accepted_tos_version: profile.acceptedTosVersion,
blocked_inbox_or_channels: profile.blockedInboxOrChannels,
email: profile.email,
is_email_already_taken: undefined,
is_email_enabled: profile.isEmailEnabled,
is_email_validated: profile.isEmailValidated,
// NOTE: We do NOT check email uniqueness in this context
is_email_already_taken: false,
is_email_enabled: profile.isEmailEnabled !== false,
is_email_validated: profile.isEmailValidated !== false,
is_inbox_enabled: profile.isInboxEnabled === true,
is_webhook_enabled: profile.isWebhookEnabled === true,
preferred_languages: profile.preferredLanguages,
Expand Down
2 changes: 1 addition & 1 deletion CreateSubscription/__tests__/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const aFakeApimProductContract: ProductContract = {
const aFakeApimSubscriptionContract: SubscriptionContract = {
allowTracing: false,
createdDate: new Date(),
displayName: null,
displayName: undefined,
endDate: undefined,
expirationDate: undefined,
id: "subscription-id",
Expand Down
3 changes: 3 additions & 0 deletions CreateUser/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ export function CreateUserHandler(
taskResults.apimClient.user.createOrUpdate(
azureApimConfig.apimResourceGroup,
azureApimConfig.apim,
// TODO: Implement a validation step to ensure the existence of `objectId`
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
taskResults.objectId,
{
email: userPayload.email,
Expand Down
52 changes: 40 additions & 12 deletions DeleteUserDataActivity/__tests__/backupAndDelete.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,17 @@ import { IBlobServiceInfo } from "../types";
import { AuthenticationLockServiceMock } from "../../__mocks__/authenticationLockService.mock";
import { IProfileEmailWriter } from "@pagopa/io-functions-commons/dist/src/utils/unique_email_enforcement";

const asyncIteratorOf = <T>(items: T[]): AsyncIterator<T[]> => {
const data = [...items];
return {
next: async () => {
const value = data.shift();
return {
done: typeof value === "undefined",
value: [value]
};
}
};
};
export async function* asyncIteratorOf<T>(items: T[]) {
for (const item of items) {
yield [item];
}
}

export async function* errorMessageIterator(error: any) {
//Sonarcloud requires at least one `yield` before `throw` operation
yield [E.right(aRetrievedMessageWithContent)];
throw error;
}

// MessageContentBlobService
const messageContentBlobService = ({} as unknown) as BlobService;
Expand Down Expand Up @@ -289,6 +288,35 @@ describe(`backupAndDeleteAllUserData`, () => {
expect(E.isRight(result)).toBe(true);
});

it("should stop if an error occurred retrieving messages", async () => {
const cosmosError = { kind: "COSMOS_ERROR_RESPONSE" };
mockFindMessages.mockImplementationOnce(() =>
TE.of(errorMessageIterator(cosmosError))
);

const result = await backupAndDeleteAllUserData({
authenticationLockService,
messageContentBlobService,
messageModel,
messageStatusModel,
messageViewModel,
notificationModel,
notificationStatusModel,
profileEmailsRepository,
profileModel,
servicePreferencesModel,
userDataBackup,
fiscalCode: aFiscalCode
})();

expect(result).toEqual(
E.left({
kind: "QUERY_FAILURE",
reason: `CosmosError: ${JSON.stringify(cosmosError)}`
})
);
});

it("should stop if there is an error while looking for a message View (404)", async () => {
mockFindMessageView.mockImplementationOnce(() =>
TE.left({
Expand Down
40 changes: 40 additions & 0 deletions DeleteUserDataActivity/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { CosmosErrors } from "@pagopa/io-functions-commons/dist/src/utils/cosmosdb_model";

import { isCosmosErrors } from "../utils";

describe("utils", () => {
// --------------
// Just a bunch of types needed for creating a tuple from an union type
// See https://www.hacklewayne.com/typescript-convert-union-to-tuple-array-yes-but-how
type Contra<T> = T extends any ? (arg: T) => void : never;
type InferContra<T> = [T] extends [(arg: infer I) => void] ? I : never;
type PickOne<T> = InferContra<InferContra<Contra<Contra<T>>>>;
type Union2Tuple<T> = PickOne<T> extends infer U // assign PickOne<T> to U
? Exclude<T, U> extends never // T and U are the same
? [T]
: [...Union2Tuple<Exclude<T, U>>, U] // recursion
: never;
// --------------

type CosmosErrorsTypesTuple = Union2Tuple<CosmosErrors["kind"]>;

// NOTE: If a new cosmos error is added, the following initialization will not compile,
// forcing us to update `CosmosErrorsTypes` with the new value
const values: CosmosErrorsTypesTuple = [
"COSMOS_EMPTY_RESPONSE",
"COSMOS_CONFLICT_RESPONSE",
"COSMOS_DECODING_ERROR",
"COSMOS_ERROR_RESPONSE"
];

it.each(values)(
"isCosmosErrors should return true if error is a CosmosError of type %s",
v => {
expect(isCosmosErrors({ kind: v })).toBe(true);
}
);

it("isCosmosErrors should return false if error is not a CosmosError", () => {
expect(isCosmosErrors({ kind: "ANOTHER_ERROR" })).toBe(false);
});
});
36 changes: 21 additions & 15 deletions DeleteUserDataActivity/backupAndDelete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import * as crypto from "crypto";

import { BlobService } from "azure-storage";
import { sequenceT } from "fp-ts/lib/Apply";
import * as A from "fp-ts/lib/Array";
import * as ROA from "fp-ts/lib/ReadonlyArray";
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 { array, flatten, rights } from "fp-ts/lib/Array";
import { MessageContent } from "@pagopa/io-functions-commons/dist/generated/definitions/MessageContent";
import {
RetrievedMessage,
Expand Down Expand Up @@ -40,6 +39,7 @@ import {
QueryFailure
} from "./types";
import {
isCosmosErrors,
saveDataToBlob,
toDocumentDeleteFailure,
toQueryFailure
Expand Down Expand Up @@ -67,18 +67,18 @@ const executeRecursiveBackupAndDelete = <T>(
TE.mapLeft(toQueryFailure),
TE.chainW(e =>
e.done
? TE.of([])
? TE.of<DataFailure, ReadonlyArray<T>>([])
: e.value.some(E.isLeft)
? TE.left(
? TE.left<DataFailure, ReadonlyArray<T>>(
toQueryFailure(new Error("Some elements are not typed correctly"))
)
: TE.of(rights(e.value))
: TE.of<DataFailure, ReadonlyArray<T>>(ROA.rights(e.value))
),
// executes backup&delete for this set of items
TE.chainW(items =>
pipe(
items,
A.map((item: T) =>
ROA.map((item: T) =>
pipe(
sequenceT(TE.ApplicativeSeq)<
DataFailure,
Expand All @@ -104,8 +104,8 @@ const executeRecursiveBackupAndDelete = <T>(
TE.map(([_, __, nextResults]) => [item, ...nextResults])
)
),
A.sequence(TE.ApplicativePar),
TE.map(flatten)
ROA.sequence(TE.ApplicativePar),
TE.map(ROA.flatten)
)
)
);
Expand Down Expand Up @@ -365,7 +365,7 @@ const backupAndDeleteMessageView = ({
}): TE.TaskEither<DataFailure, O.Option<RetrievedMessageView>> =>
pipe(
messageViewModel.find([message.id, message.fiscalCode]),
TE.chain(TE.fromOption(() => undefined)),
TE.chainW(TE.fromOption(() => undefined)),
TE.foldW(
_ =>
// unfortunately, a document not found is threated like a query error
Expand Down Expand Up @@ -443,7 +443,7 @@ const backupAndDeleteMessageContent = ({
}): TE.TaskEither<DataFailure, O.Option<MessageContent>> =>
pipe(
messageModel.getContentFromBlob(messageContentBlobService, message.id),
TE.chain(TE.fromOption(() => undefined)),
TE.chainW(TE.fromOption(() => undefined)),
TE.foldW(
_ =>
// unfortunately, a document not found is threated like a query error
Expand Down Expand Up @@ -587,19 +587,25 @@ const backupAndDeleteAllMessagesData = ({
pipe(
messageModel.findMessages(fiscalCode),
TE.mapLeft(toQueryFailure),
TE.chain(iter =>
TE.tryCatch(() => asyncIteratorToArray(iter), toQueryFailure)
TE.chainW(iter =>
TE.tryCatch(
() => asyncIteratorToArray(iter),
err =>
err instanceof Error || isCosmosErrors(err)
? toQueryFailure(err)
: toQueryFailure(E.toError(err))
)
),
TE.map(flatten),
TE.map(ROA.flatten),
TE.chainW(results =>
results.some(E.isLeft)
? TE.left(
toQueryFailure(
new Error("Cannot decode some element due to decoding errors")
)
)
: array.sequence(TE.ApplicativeSeq)(
rights(results).map(message => {
: ROA.sequence(TE.ApplicativeSeq)(
ROA.rights(results).map(message => {
// cast needed because findMessages has a wrong signature
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const retrievedMessage = (message as any) as RetrievedMessageWithoutContent;
Expand Down
18 changes: 18 additions & 0 deletions DeleteUserDataActivity/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import * as t from "io-ts";
import { Context } from "@azure/functions";
import { BlobService } from "azure-storage";
import * as TE from "fp-ts/lib/TaskEither";
import { CosmosErrors } from "@pagopa/io-functions-commons/dist/src/utils/cosmosdb_model";
import { pipe } from "fp-ts/lib/function";

import { enumType } from "@pagopa/ts-commons/lib/types";
import {
ActivityResultFailure,
BlobCreationFailure,
Expand All @@ -11,6 +14,21 @@ import {
QueryFailure
} from "./types";

// Cosmos Errors
export enum CosmosErrorsTypes {
"COSMOS_EMPTY_RESPONSE" = "COSMOS_EMPTY_RESPONSE",
"COSMOS_CONFLICT_RESPONSE" = "COSMOS_CONFLICT_RESPONSE",
"COSMOS_DECODING_ERROR" = "COSMOS_DECODING_ERROR",
"COSMOS_ERROR_RESPONSE" = "COSMOS_ERROR_RESPONSE"
}

const CosmosErrorsTypesC = t.interface({
kind: enumType<CosmosErrorsTypes>(CosmosErrorsTypes, "kind")
});

export const isCosmosErrors = (error: unknown): error is CosmosErrors =>
CosmosErrorsTypesC.is(error);

/**
* To be used for exhaustive checks
*/
Expand Down
17 changes: 5 additions & 12 deletions ExtractUserDataActivity/__tests__/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,18 +113,11 @@ const messageModelMock = ({
} 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]
};
}
};
};
export async function* asyncIteratorOf<T>(items: T[]) {
for (const item of items) {
yield [item];
}
}

const mockDeleteServicePreferences = jest.fn<
ReturnType<InstanceType<typeof ServicePreferencesDeletableModel>["delete"]>,
Expand Down
Loading
Loading