From f3586d49894d1334c393be22b154629a34bfe7f4 Mon Sep 17 00:00:00 2001 From: Guillim Date: Wed, 12 Feb 2025 14:22:13 +0100 Subject: [PATCH] HandleAlias (#10156) If a user has microsoft aliases, they will be taken into account, in a similar way as we did for google --- .../microsoft-email-alias-manager.service.ts | 41 +++++++ .../microsoft/mocks/microsoft-api-examples.ts | 9 ++ .../email-alias-manager.module.ts | 9 +- .../email-alias-manager.service.spec.ts | 102 ++++++++++++++++++ .../services/email-alias-manager.service.ts | 8 +- 5 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 packages/twenty-server/src/modules/connected-account/email-alias-manager/drivers/microsoft/microsoft-email-alias-manager.service.ts create mode 100644 packages/twenty-server/src/modules/connected-account/email-alias-manager/drivers/microsoft/mocks/microsoft-api-examples.ts create mode 100644 packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.spec.ts diff --git a/packages/twenty-server/src/modules/connected-account/email-alias-manager/drivers/microsoft/microsoft-email-alias-manager.service.ts b/packages/twenty-server/src/modules/connected-account/email-alias-manager/drivers/microsoft/microsoft-email-alias-manager.service.ts new file mode 100644 index 000000000..c4fa0ff0d --- /dev/null +++ b/packages/twenty-server/src/modules/connected-account/email-alias-manager/drivers/microsoft/microsoft-email-alias-manager.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@nestjs/common'; + +import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { MicrosoftClientProvider } from 'src/modules/messaging/message-import-manager/drivers/microsoft/providers/microsoft-client.provider'; + +@Injectable() +export class MicrosoftEmailAliasManagerService { + constructor( + private readonly microsoftClientProvider: MicrosoftClientProvider, + ) {} + + public async getHandleAliases( + connectedAccount: ConnectedAccountWorkspaceEntity, + ) { + const microsoftClient = + await this.microsoftClientProvider.getMicrosoftClient(connectedAccount); + + const response = await microsoftClient + .api('/me?$select=proxyAddresses') + .get() + .catch((error) => { + throw new Error(`Failed to fetch email aliases: ${error.message}`); + }); + + const proxyAddresses = response.proxyAddresses; + + const handleAliases = + proxyAddresses + ?.filter((address) => { + return address.startsWith('SMTP:') === false; + }) + .map((address) => { + return address.replace('smtp:', '').toLowerCase(); + }) + .filter((address) => { + return address !== ''; + }) || []; + + return handleAliases; + } +} diff --git a/packages/twenty-server/src/modules/connected-account/email-alias-manager/drivers/microsoft/mocks/microsoft-api-examples.ts b/packages/twenty-server/src/modules/connected-account/email-alias-manager/drivers/microsoft/mocks/microsoft-api-examples.ts new file mode 100644 index 000000000..25f4244b9 --- /dev/null +++ b/packages/twenty-server/src/modules/connected-account/email-alias-manager/drivers/microsoft/mocks/microsoft-api-examples.ts @@ -0,0 +1,9 @@ +export const microsoftGraphMeResponseWithProxyAddresses = { + '@odata.context': + 'https://graph.microsoft.com/v1.0/$metadata#users(proxyAddresses)/$entity', + proxyAddresses: [ + 'SMTP:bertrand1@domain.onmicrosoft.com', + 'smtp:bertrand2@domain.onmicrosoft.com', + 'smtp:bertrand3@otherdomain.com', + ], +}; diff --git a/packages/twenty-server/src/modules/connected-account/email-alias-manager/email-alias-manager.module.ts b/packages/twenty-server/src/modules/connected-account/email-alias-manager/email-alias-manager.module.ts index a634ff0fe..364d030b0 100644 --- a/packages/twenty-server/src/modules/connected-account/email-alias-manager/email-alias-manager.module.ts +++ b/packages/twenty-server/src/modules/connected-account/email-alias-manager/email-alias-manager.module.ts @@ -1,12 +1,19 @@ import { Module } from '@nestjs/common'; import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/google-email-alias-manager.service'; +import { MicrosoftEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/microsoft-email-alias-manager.service'; import { EmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/services/email-alias-manager.service'; import { OAuth2ClientManagerModule } from 'src/modules/connected-account/oauth2-client-manager/oauth2-client-manager.module'; +import { MicrosoftClientProvider } from 'src/modules/messaging/message-import-manager/drivers/microsoft/providers/microsoft-client.provider'; @Module({ imports: [OAuth2ClientManagerModule], - providers: [EmailAliasManagerService, GoogleEmailAliasManagerService], + providers: [ + EmailAliasManagerService, + GoogleEmailAliasManagerService, + MicrosoftEmailAliasManagerService, + MicrosoftClientProvider, + ], exports: [EmailAliasManagerService], }) export class EmailAliasManagerModule {} diff --git a/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.spec.ts b/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.spec.ts new file mode 100644 index 000000000..e5a4452ef --- /dev/null +++ b/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.spec.ts @@ -0,0 +1,102 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { ConnectedAccountProvider } from 'twenty-shared'; +import { Repository } from 'typeorm'; + +import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/google-email-alias-manager.service'; +import { MicrosoftEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/microsoft-email-alias-manager.service'; +import { microsoftGraphMeResponseWithProxyAddresses } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/mocks/microsoft-api-examples'; +import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { MicrosoftClientProvider } from 'src/modules/messaging/message-import-manager/drivers/microsoft/providers/microsoft-client.provider'; + +import { EmailAliasManagerService } from './email-alias-manager.service'; + +describe('Email Alias Manager Service', () => { + let emailAliasManagerService: EmailAliasManagerService; + let microsoftEmailAliasManagerService: MicrosoftEmailAliasManagerService; + let connectedAccountRepository: Partial< + Repository + >; + + beforeEach(async () => { + connectedAccountRepository = { + update: jest.fn().mockResolvedValue((arg) => arg), + }; + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: TwentyORMManager, + useValue: { + getRepository: jest + .fn() + .mockResolvedValue(connectedAccountRepository), + }, + }, + EmailAliasManagerService, + { + provide: GoogleEmailAliasManagerService, + useValue: {}, + }, + MicrosoftEmailAliasManagerService, + { + provide: MicrosoftClientProvider, + useValue: { + getMicrosoftClient: jest.fn().mockResolvedValue({ + api: jest.fn().mockReturnValue({ + get: jest + .fn() + .mockResolvedValue( + microsoftGraphMeResponseWithProxyAddresses, + ), + }), + }), + }, + }, + ], + }).compile(); + + emailAliasManagerService = module.get( + EmailAliasManagerService, + ); + microsoftEmailAliasManagerService = + module.get( + MicrosoftEmailAliasManagerService, + ); + }); + + it('Service should be defined', () => { + expect(emailAliasManagerService).toBeDefined(); + }); + + describe('Refresh handle aliases for Microsoft', () => { + it('Should refresh Microsoft handle aliases successfully', async () => { + const mockConnectedAccount: Partial = { + id: 'test-id', + provider: ConnectedAccountProvider.MICROSOFT, + refreshToken: 'test-refresh-token', + }; + + const expectedAliases = + 'bertrand2@domain.onmicrosoft.com,bertrand3@otherdomain.com'; + + jest.spyOn(microsoftEmailAliasManagerService, 'getHandleAliases'); + + await emailAliasManagerService.refreshHandleAliases( + mockConnectedAccount as ConnectedAccountWorkspaceEntity, + ); + + expect( + microsoftEmailAliasManagerService.getHandleAliases, + ).toHaveBeenCalledWith(mockConnectedAccount); + + expect(connectedAccountRepository.update).toHaveBeenCalledWith( + { id: mockConnectedAccount.id }, + { + handleAliases: expectedAliases, + }, + ); + }); + }); +}); diff --git a/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.ts b/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.ts index e4a583e92..64882284a 100644 --- a/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.ts +++ b/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.ts @@ -2,12 +2,14 @@ import { Injectable } from '@nestjs/common'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/google-email-alias-manager.service'; +import { MicrosoftEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/microsoft-email-alias-manager.service'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; @Injectable() export class EmailAliasManagerService { constructor( private readonly googleEmailAliasManagerService: GoogleEmailAliasManagerService, + private readonly microsoftEmailAliasManagerService: MicrosoftEmailAliasManagerService, private readonly twentyORMManager: TwentyORMManager, ) {} @@ -18,7 +20,11 @@ export class EmailAliasManagerService { switch (connectedAccount.provider) { case 'microsoft': - return; + handleAliases = + await this.microsoftEmailAliasManagerService.getHandleAliases( + connectedAccount, + ); + break; case 'google': handleAliases = await this.googleEmailAliasManagerService.getHandleAliases(