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-1124] backup&delete authentication lock data #226

Merged
merged 8 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CreateDevelopmentProfile/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ 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,
is_inbox_enabled: profile.isInboxEnabled === true,
Expand Down
157 changes: 157 additions & 0 deletions DeleteUserDataActivity/__tests__/authenticationLockService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { RestError } from "@azure/data-tables";

import * as E from "fp-ts/Either";
import * as ROA from "fp-ts/ReadonlyArray";

import AuthenticationLockService from "../authenticationLockService";

import {
aFiscalCode,
aNotReleasedData,
anUnlockCode,
anothernUnlockCode,
brokeEntityProfileLockedRecordIterator,
errorProfileLockedRecordIterator,
getProfileLockedRecordIterator,
listLockedProfileEntitiesMock,
lockedProfileTableClient,
submitTransactionMock
} from "../../__mocks__/lockedProfileTableClient";
import { UnlockCode } from "../../generated/definitions/UnlockCode";

// -------------------------------------------
// Variables
// -------------------------------------------

const service = new AuthenticationLockService(lockedProfileTableClient);

describe("AuthenticationLockService#getAllUserAuthenticationLockData", () => {
it("should return an empty array if query returns no records from table storage", async () => {
const result = await service.getAllUserAuthenticationLockData(
aFiscalCode
)();

expect(result).toEqual(E.right([]));
expect(listLockedProfileEntitiesMock).toHaveBeenCalledWith({
queryOptions: {
filter: `PartitionKey eq '${aFiscalCode}'`
}
});
});

it.each`
title | records
${"one record"} | ${[aNotReleasedData]}
${"more records"} | ${[aNotReleasedData, { ...aNotReleasedData, rowKey: anothernUnlockCode, Released: true }]}
`(
"should return all the records, if $title are found in table storage",
async ({ records }) => {
listLockedProfileEntitiesMock.mockImplementationOnce(() =>
getProfileLockedRecordIterator(records)
);

const result = await service.getAllUserAuthenticationLockData(
aFiscalCode
)();

expect(result).toEqual(E.right(records));
}
);

it("should return an error if something went wrong retrieving the records", async () => {
listLockedProfileEntitiesMock.mockImplementationOnce(
errorProfileLockedRecordIterator
);

const result = await service.getAllUserAuthenticationLockData(
aFiscalCode
)();

expect(result).toEqual(E.left(Error("an Error")));
});

it("should return an error if something went wrong decoding a record", async () => {
listLockedProfileEntitiesMock.mockImplementationOnce(
brokeEntityProfileLockedRecordIterator
);

const result = await service.getAllUserAuthenticationLockData(
aFiscalCode
)();

expect(result).toEqual(
E.left(
Error(
'value ["CF"] at [root.0.0.0.partitionKey] is not a valid [string that matches the pattern "^[A-Z]{6}[0-9LMNPQRSTUV]{2}[ABCDEHLMPRST][0-9LMNPQRSTUV]{2}[A-Z][0-9LMNPQRSTUV]{3}[A-Z]$"] / value [undefined] at [root.0.0.1.Released] is not a valid [boolean] / value ["CF"] at [root.0.1.partitionKey] is not a valid [string that matches the pattern "^[A-Z]{6}[0-9LMNPQRSTUV]{2}[ABCDEHLMPRST][0-9LMNPQRSTUV]{2}[A-Z][0-9LMNPQRSTUV]{3}[A-Z]$"]'
)
)
);
});
gquadrati marked this conversation as resolved.
Show resolved Hide resolved
});

describe("AuthenticationLockService#deleteUserAuthenticationLockData", () => {
beforeEach(() => {
jest.clearAllMocks();
});

const generateUnlockCodes = (number: number) =>
[...Array(number).keys()].map(i => i.toString().padStart(9, "0"));
gquadrati marked this conversation as resolved.
Show resolved Hide resolved
it.each`
scenario | items
${"with less than 100 items"} | ${generateUnlockCodes(50)}
${"with more than 100 items"} | ${generateUnlockCodes(150)}
`(
"should return true when records delete transaction succeded $scenario",
async ({ items }: { items: UnlockCode[] }) => {
const result = await service.deleteUserAuthenticationLockData(
aFiscalCode,
items
)();

expect(result).toEqual(E.right(true));

let i = 1;
for (const chunk of ROA.chunksOf(100)(items)) {
expect(submitTransactionMock).toHaveBeenNthCalledWith(
i,
chunk.map(unlockCode => [
"delete",
{
partitionKey: aFiscalCode,
rowKey: unlockCode
}
])
);

i++;
}
}
);

it("should return an Error when at least one CF-unlock code was not found", async () => {
submitTransactionMock.mockRejectedValueOnce(
new RestError("Not Found", { statusCode: 404 })
);
const result = await service.deleteUserAuthenticationLockData(aFiscalCode, [
anUnlockCode,
anothernUnlockCode
])();

expect(result).toEqual(
E.left(new Error("Something went wrong deleting the records"))
);
});

it("should return an Error when an error occurred deleting the records", async () => {
submitTransactionMock.mockRejectedValueOnce(
new RestError("An Error", { statusCode: 500 })
);
const result = await service.deleteUserAuthenticationLockData(aFiscalCode, [
anUnlockCode
])();

expect(result).toEqual(
E.left(new Error("Something went wrong deleting the records"))
);
});
});
12 changes: 12 additions & 0 deletions DeleteUserDataActivity/__tests__/backupAndDelete.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
} from "../../__mocks__/mocks";
import { backupAndDeleteAllUserData } from "../backupAndDelete";
import { IBlobServiceInfo } from "../types";
import { AuthenticationLockServiceMock } from "../../__mocks__/authenticationLockService.mock";

const asyncIteratorOf = <T>(items: T[]): AsyncIterator<T[]> => {
const data = [...items];
Expand Down Expand Up @@ -150,10 +151,13 @@ const userDataBackup = {
folder: "folder"
} as IBlobServiceInfo;

const authenticationLockService = AuthenticationLockServiceMock;

describe(`backupAndDeleteAllUserData`, () => {
beforeEach(() => jest.clearAllMocks());
it("should work if there are no errors", async () => {
const result = await backupAndDeleteAllUserData({
authenticationLockService,
messageContentBlobService,
messageModel,
messageStatusModel,
Expand All @@ -179,6 +183,7 @@ describe(`backupAndDeleteAllUserData`, () => {
it("should not stop if a content is not found for a message", async () => {
mockGetContentFromBlob.mockImplementationOnce(() => TE.of(none));
const result = await backupAndDeleteAllUserData({
authenticationLockService,
messageContentBlobService,
messageModel,
messageStatusModel,
Expand All @@ -204,6 +209,7 @@ describe(`backupAndDeleteAllUserData`, () => {
it("should not stop if there is an error while looking for a message content", async () => {
mockGetContentFromBlob.mockImplementationOnce(() => TE.left(new Error("")));
const result = await backupAndDeleteAllUserData({
authenticationLockService,
messageContentBlobService,
messageModel,
messageStatusModel,
Expand All @@ -222,6 +228,7 @@ describe(`backupAndDeleteAllUserData`, () => {
it("should not stop if a notification is not found for a message (none)", async () => {
mockFindNotificationForMessage.mockImplementationOnce(() => TE.of(none));
const result = await backupAndDeleteAllUserData({
authenticationLockService,
messageContentBlobService,
messageModel,
messageStatusModel,
Expand All @@ -245,6 +252,7 @@ describe(`backupAndDeleteAllUserData`, () => {
})
);
const result = await backupAndDeleteAllUserData({
authenticationLockService,
messageContentBlobService,
messageModel,
messageStatusModel,
Expand All @@ -268,6 +276,7 @@ describe(`backupAndDeleteAllUserData`, () => {
})
);
const result = await backupAndDeleteAllUserData({
authenticationLockService,
messageContentBlobService,
messageModel,
messageStatusModel,
Expand All @@ -289,6 +298,7 @@ describe(`backupAndDeleteAllUserData`, () => {
asyncIteratorOf([])
);
const result = await backupAndDeleteAllUserData({
authenticationLockService,
messageContentBlobService,
messageModel,
messageStatusModel,
Expand Down Expand Up @@ -316,6 +326,7 @@ describe(`backupAndDeleteAllUserData`, () => {
asyncIteratorOf([E.left([{} as ValidationError])])
);
const result = await backupAndDeleteAllUserData({
authenticationLockService,
messageContentBlobService,
messageModel,
messageStatusModel,
Expand Down Expand Up @@ -343,6 +354,7 @@ describe(`backupAndDeleteAllUserData`, () => {
TE.left(toCosmosErrorResponse("") as CosmosErrors)
);
const result = await backupAndDeleteAllUserData({
authenticationLockService,
messageContentBlobService,
messageModel,
messageStatusModel,
Expand Down
Loading