diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts index b05f4838b04..bdd00715305 100644 --- a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts +++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts @@ -131,8 +131,7 @@ export class ImapSmtpCaldavService { }); try { - await client.listCalendars(); - await client.validateSyncCollectionSupport(); + await client.listCalendarsAndValidateSync(); } catch (error) { this.logger.error( `CALDAV connection failed: ${error.message}`, diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/lib/caldav.client.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/lib/caldav.client.ts index ab1b260b21b..6abba079f83 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/lib/caldav.client.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/lib/caldav.client.ts @@ -108,16 +108,24 @@ export class CalDAVClient { }); } + private async fetchDAVCalendars(): Promise< + (Omit & { + displayName?: string | Record; + })[] + > { + const account = await this.getAccount(); + + return (await fetchCalendars({ + account, + headers: this.headers, + })) as (Omit & { + displayName?: string | Record; + })[]; + } + async listCalendars(): Promise { try { - const account = await this.getAccount(); - - const calendars = (await fetchCalendars({ - account, - headers: this.headers, - })) as (Omit & { - displayName?: string | Record; - })[]; + const calendars = await this.fetchDAVCalendars(); return calendars.reduce((result, calendar) => { if (!calendar.components?.includes('VEVENT')) return result; @@ -146,13 +154,48 @@ export class CalDAVClient { } async validateSyncCollectionSupport(): Promise { - const account = await this.getAccount(); + const calendars = await this.fetchDAVCalendars(); - const calendars = await fetchCalendars({ - account, - headers: this.headers, - }); + this.assertSyncCollectionSupported(calendars); + } + async listCalendarsAndValidateSync(): Promise { + try { + const calendars = await this.fetchDAVCalendars(); + + this.assertSyncCollectionSupported(calendars); + + return calendars.reduce((result, calendar) => { + if (!calendar.components?.includes('VEVENT')) return result; + + result.push({ + id: calendar.url, + url: calendar.url, + name: + typeof calendar.displayName === 'string' + ? calendar.displayName + : 'Unnamed Calendar', + isPrimary: false, + }); + + return result; + }, []); + } catch (error) { + this.logger.error( + `Error in ${CalDavGetEventsService.name} - getCalendarEvents`, + error.code, + error, + ); + + throw error; + } + } + + private assertSyncCollectionSupported( + calendars: (Omit & { + displayName?: string | Record; + })[], + ): void { const eventCalendar = calendars.find((calendar) => calendar.components?.includes('VEVENT'), ); diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.spec.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.spec.ts index 7b2eeb522c9..62be1f229c1 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.spec.ts @@ -129,15 +129,21 @@ describe('SyncMessageFoldersService', () => { mockMessageFolderRepository = { delete: jest.fn(), update: jest.fn().mockResolvedValue(undefined), - save: jest.fn().mockImplementation(async (folder) => { - createdFolderRecords.push({ - ...folder, - id: `new-folder-${createdFolderRecords.length}-${Math.random().toString(36).substring(7)}`, - isSynced: false, - syncCursor: null, - pendingSyncAction: MessageFolderPendingSyncAction.NONE, - externalId: folder.externalId as string, - }); + save: jest.fn().mockImplementation(async (folderOrFolders) => { + const folders = Array.isArray(folderOrFolders) + ? folderOrFolders + : [folderOrFolders]; + + for (const folder of folders) { + createdFolderRecords.push({ + ...folder, + id: `new-folder-${createdFolderRecords.length}-${Math.random().toString(36).substring(7)}`, + isSynced: false, + syncCursor: null, + pendingSyncAction: MessageFolderPendingSyncAction.NONE, + externalId: folder.externalId as string, + }); + } }), find: jest.fn().mockImplementation(async ({ where }) => { if (!where?.externalId) { @@ -227,22 +233,22 @@ describe('SyncMessageFoldersService', () => { }); expect(mockMessageFolderRepository.save).toHaveBeenCalledWith( - expect.objectContaining({ - workspaceId, - name: 'INBOX', - externalId: 'inbox-ext', - messageChannelId: 'channel-123', - isSentFolder: false, - }), - ); - expect(mockMessageFolderRepository.save).toHaveBeenCalledWith( - expect.objectContaining({ - workspaceId, - name: 'Sent', - externalId: 'sent-ext', - messageChannelId: 'channel-123', - isSentFolder: true, - }), + expect.arrayContaining([ + expect.objectContaining({ + workspaceId, + name: 'INBOX', + externalId: 'inbox-ext', + messageChannelId: 'channel-123', + isSentFolder: false, + }), + expect.objectContaining({ + workspaceId, + name: 'Sent', + externalId: 'sent-ext', + messageChannelId: 'channel-123', + isSentFolder: true, + }), + ]), ); expect(result).toHaveLength(2); }); @@ -274,11 +280,13 @@ describe('SyncMessageFoldersService', () => { }); expect(mockMessageFolderRepository.save).toHaveBeenCalledWith( - expect.objectContaining({ - workspaceId, - name: 'Projects', - parentFolderId: 'parent-folder-id', - }), + expect.arrayContaining([ + expect.objectContaining({ + workspaceId, + name: 'Projects', + parentFolderId: 'parent-folder-id', + }), + ]), ); }); }); @@ -508,10 +516,12 @@ describe('SyncMessageFoldersService', () => { expect.objectContaining({ name: 'New Name' }), ); expect(mockMessageFolderRepository.save).toHaveBeenCalledWith( - expect.objectContaining({ - workspaceId, - externalId: 'new-ext', - }), + expect.arrayContaining([ + expect.objectContaining({ + workspaceId, + externalId: 'new-ext', + }), + ]), ); expect(result).toHaveLength(4); expect(result).toContainEqual( diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.ts index 981e536af9b..1f3d01f9dd1 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.ts @@ -154,12 +154,12 @@ export class SyncMessageFoldersService { } if (foldersToCreate.length > 0) { - for (const folderToCreate of foldersToCreate) { - await this.messageFolderRepository.save({ + await this.messageFolderRepository.save( + foldersToCreate.map((folderToCreate) => ({ ...folderToCreate, workspaceId, - }); - } + })), + ); } const createdFolders =