diff --git a/src/utils/unique_email_enforcement/__tests__/index.test.ts b/src/utils/unique_email_enforcement/__tests__/index.test.ts index 41d47541..69915462 100644 --- a/src/utils/unique_email_enforcement/__tests__/index.test.ts +++ b/src/utils/unique_email_enforcement/__tests__/index.test.ts @@ -39,8 +39,7 @@ describe("isEmailAlreadyTaken", () => { ({ entries, expected }) => { const result = isEmailAlreadyTaken(mocks.email)({ profileEmails: { - list: generateProfileEmails(entries), - get: jest.fn() + list: generateProfileEmails(entries) } }); expect(result).resolves.toBe(expected); diff --git a/src/utils/unique_email_enforcement/__tests__/storage.test.ts b/src/utils/unique_email_enforcement/__tests__/storage.test.ts index dc472db3..768bebd0 100644 --- a/src/utils/unique_email_enforcement/__tests__/storage.test.ts +++ b/src/utils/unique_email_enforcement/__tests__/storage.test.ts @@ -69,27 +69,6 @@ const tableClient = new MockedTableClient( ); describe("DataTableProfileEmailsRepository", () => { - describe("get", () => { - it.each(["citizen@email.test.pagopa.it", "CITIZEN@EMAIL.TEST.PAGOPA.IT"])( - "normalizes input e-mail addresses", - async email => { - const repo = new DataTableProfileEmailsRepository(tableClient); - const profileEmail = ProfileEmail.decode({ - email, - fiscalCode: "RLDBSV36A78Y792X" - }); - if (E.isRight(profileEmail)) { - await repo.get(profileEmail.right); - expect(tableClient.getEntity).toHaveBeenCalledWith( - "citizen@email.test.pagopa.it", - "RLDBSV36A78Y792X" - ); - } - expect.hasAssertions(); - } - ); - }); - describe("list", () => { it("normalizes input e-mail address", async () => { const repo = new DataTableProfileEmailsRepository(tableClient); diff --git a/src/utils/unique_email_enforcement/index.ts b/src/utils/unique_email_enforcement/index.ts index 8ac8da22..5bdcfa6b 100644 --- a/src/utils/unique_email_enforcement/index.ts +++ b/src/utils/unique_email_enforcement/index.ts @@ -10,7 +10,6 @@ export const ProfileEmail = t.type({ export type ProfileEmail = t.TypeOf; export interface IProfileEmailReader { - readonly get: (p: ProfileEmail) => Promise; readonly list: (filter: EmailString) => AsyncIterableIterator; } @@ -19,6 +18,27 @@ export interface IProfileEmailWriter { readonly insert: (p: ProfileEmail) => Promise; } +type ProfileEmailWriterErrorCause = + | "ENTITY_NOT_FOUND" + | "DUPLICATE_ENTITY" + | "STORAGE_ERROR"; + +/* eslint-disable functional/prefer-readonly-type */ +export class ProfileEmailWriterError extends Error { + public name = "ProfileEmailWriterError"; + public cause: ProfileEmailWriterErrorCause; + + constructor(message: string, cause: ProfileEmailWriterErrorCause) { + super(message); + this.cause = cause; + } + + public static is(u: unknown): u is ProfileEmailWriterError { + return u instanceof Error && u.name === "ProfileEmailWriterError"; + } +} +/* eslint-enable functional/prefer-readonly-type */ + // Checks if the given e-mail is already taken // profileEmails returns all the ProfileEmail records that shares // the same e-mail. If count(records) >= 1 then the e-mail is already taken. diff --git a/src/utils/unique_email_enforcement/storage.ts b/src/utils/unique_email_enforcement/storage.ts index 021fa76d..7ec921be 100644 --- a/src/utils/unique_email_enforcement/storage.ts +++ b/src/utils/unique_email_enforcement/storage.ts @@ -2,13 +2,14 @@ import * as E from "fp-ts/lib/Either"; import * as t from "io-ts"; import { flow } from "fp-ts/lib/function"; -import { TableClient, odata } from "@azure/data-tables"; +import { TableClient, odata, RestError } from "@azure/data-tables"; import { EmailString } from "@pagopa/ts-commons/lib/strings"; import { ProfileEmail, IProfileEmailReader, - IProfileEmailWriter + IProfileEmailWriter, + ProfileEmailWriterError } from "./index"; const TableEntity = t.type({ @@ -33,30 +34,13 @@ const ProfileEmailToTableEntity = new t.Type( }) ); +const isRestError = (u: unknown): u is RestError => + u instanceof Error && u.name === "RestError"; + export class DataTableProfileEmailsRepository implements IProfileEmailReader, IProfileEmailWriter { constructor(private readonly tableClient: TableClient) {} - public async get(p: ProfileEmail): Promise { - try { - const entity = await this.tableClient.getEntity( - p.email.toLowerCase(), - p.fiscalCode - ); - const profileEmail = ProfileEmailToTableEntity.decode(entity); - if (E.isLeft(profileEmail)) { - throw new Error(`can't parse a profile email from the given entity`, { - cause: "parsing" - }); - } - return profileEmail.right; - } catch { - throw new Error( - `unable to get a profile entity from ${this.tableClient.tableName} table` - ); - } - } - // Generates an AsyncIterable public async *list(filter: EmailString): AsyncIterableIterator { const queryOptions = { @@ -89,9 +73,12 @@ export class DataTableProfileEmailsRepository try { const entity = ProfileEmailToTableEntity.encode(p); await this.tableClient.createEntity(entity); - } catch { - throw new Error( - `unable to insert a new profile entity into ${this.tableClient.tableName} table` + } catch (e) { + throw new ProfileEmailWriterError( + `unable to insert a new profile entity into ${this.tableClient.tableName} table`, + isRestError(e) && e.statusCode === 409 + ? "DUPLICATE_ENTITY" + : "STORAGE_ERROR" ); } } @@ -100,9 +87,14 @@ export class DataTableProfileEmailsRepository try { const entity = ProfileEmailToTableEntity.encode(p); await this.tableClient.deleteEntity(entity.partitionKey, entity.rowKey); - } catch { - throw new Error( - `unable to delete the specified entity from ${this.tableClient.tableName} table` + } catch (e) { + throw new ProfileEmailWriterError( + `unable to delete the specified entity from ${this.tableClient.tableName} table`, + isRestError(e) && + e.statusCode === 404 && + e.message.includes(`"ResourceNotFound"`) + ? "ENTITY_NOT_FOUND" + : "STORAGE_ERROR" ); } }