import { type Event, type MailFolder } from '@microsoft/microsoft-graph-types'; import { isNonEmptyString } from '@sniptt/guards'; import { http, HttpResponse } from 'msw'; import { isDefined } from 'twenty-shared/utils'; import { setupHttpMock } from 'test/integration/utils/http-mock.util'; import { MICROSOFT_TOKEN_URL, microsoftAuthHandlers, } from 'test/integration/microsoft/mocks/microsoft-auth-handlers.util'; import { microsoftCalendarEventsHandlers } from 'test/integration/microsoft/mocks/microsoft-calendar-events-handlers.util'; import { microsoftMailboxHandlers } from 'test/integration/microsoft/mocks/microsoft-mailbox-handlers.util'; import { createMicrosoftSubscriptionStore, microsoftWebhookSubscriptionHandlers, type MicrosoftSubscriptionStore, } from 'test/integration/microsoft/mocks/microsoft-webhook-subscription-handlers.util'; import { createMockEntityStore, type MockEntityStore, } from 'test/integration/utils/mock-entity-store.util'; const DEFAULT_FOLDERS: MailFolder[] = [ { id: 'inbox', displayName: 'Inbox' }, { id: 'sentitems', displayName: 'Sent Items' }, ]; export type MicrosoftMock = { folders: MockEntityStore; subscriptions: MicrosoftSubscriptionStore; createdMessages: Array>; patchedMessages: Array>; createdAttachments: Array>; sentMessageIds: string[]; createdCalendarEvents: Event[]; serveCalendarEvents: ( events: Event[], options?: { deltaToken?: string; removedEventIds?: string[] }, ) => void; moveMessageToFolder: (messageId: string, targetFolderId: string) => void; deleteMessage: (messageId: string) => void; failMessageFetch: (messageId: string) => void; failSubscriptionRenewal: () => void; failMessageDelta: (failure: MicrosoftGraphFailure) => void; failCalendarDelta: (failure: MicrosoftGraphFailure) => void; declineTokenRefresh: () => void; }; export type MicrosoftGraphFailure = { status: number; code: string; message: string; }; const microsoftGraphErrorResponse = ({ status, code, message, }: MicrosoftGraphFailure) => HttpResponse.json({ error: { code, message } }, { status }); export const setupMicrosoftMock = ({ handle, aliases = [], folders = DEFAULT_FOLDERS, messages = [], }: { handle: string; aliases?: string[]; folders?: MailFolder[]; messages?: Array>; }): MicrosoftMock => { const folderStore = createMockEntityStore( folders, (folder) => folder.id ?? '', ); const subscriptionStore = createMicrosoftSubscriptionStore(); const createdMessages: Array> = []; const patchedMessages: Array> = []; const createdAttachments: Array> = []; const sentMessageIds: string[] = []; const createdCalendarEvents: Event[] = []; const removedMessageIdsByFolderId: Record = {}; const recordFolderRemoval = (messageId: string) => { const message = messages.find((candidate) => candidate.id === messageId); if (!isDefined(message)) { throw new Error(`No mocked Microsoft message with id ${messageId}`); } const folderId = message.parentFolderId; if (!isNonEmptyString(folderId)) { throw new Error( `Mocked Microsoft message ${messageId} has no parentFolderId`, ); } removedMessageIdsByFolderId[folderId] = [ ...(removedMessageIdsByFolderId[folderId] ?? []), messageId, ]; return message; }; const httpMock = setupHttpMock( ...microsoftAuthHandlers(handle, aliases), ...microsoftMailboxHandlers( folderStore, messages, removedMessageIdsByFolderId, ), ...microsoftWebhookSubscriptionHandlers(subscriptionStore), http.post('*/me/messages', async ({ request }) => { const message = (await request.json()) as Record; const id = `microsoft-message-${createdMessages.length + 1}`; createdMessages.push(message); return HttpResponse.json({ id, internetMessageId: `<${id}@example.com>`, conversationId: `microsoft-conversation-${createdMessages.length}`, }); }), http.post( '*/me/messages/:messageId/attachments', async ({ request, params }) => { const attachment = (await request.json()) as Record; createdAttachments.push({ messageId: params.messageId as string, ...attachment, }); return HttpResponse.json({ id: `microsoft-attachment-${createdAttachments.length}`, ...attachment, }); }, ), http.post('*/me/messages/:messageId/send', ({ params }) => { sentMessageIds.push(params.messageId as string); return new HttpResponse(null, { status: 202 }); }), http.delete( '*/me/messages/:messageId', () => new HttpResponse(null, { status: 204 }), ), http.get('*/me/messages', () => HttpResponse.json({ value: [ { id: 'microsoft-parent-message', internetMessageId: '', }, ], }), ), http.post('*/me/messages/:messageId/createReply', () => HttpResponse.json({ id: 'microsoft-reply-message', internetMessageId: '', conversationId: 'microsoft-parent-conversation', }), ), http.patch('*/me/messages/:messageId', async ({ request }) => { const message = (await request.json()) as Record; patchedMessages.push(message); return HttpResponse.json({ internetMessageId: '', conversationId: 'microsoft-parent-conversation', }); }), http.post('*/me/calendar/events', async ({ request }) => { const event = (await request.json()) as Event; const id = `microsoft-created-calendar-event-${createdCalendarEvents.length + 1}`; const createdEvent: Event = { ...event, id, iCalUId: `${id}@microsoft.com`, isCancelled: false, createdDateTime: '2026-08-13T00:00:00.000Z', lastModifiedDateTime: '2026-08-13T00:00:00.000Z', }; createdCalendarEvents.push(createdEvent); return HttpResponse.json(createdEvent); }), ); return { folders: folderStore, subscriptions: subscriptionStore, createdMessages, patchedMessages, createdAttachments, sentMessageIds, createdCalendarEvents, serveCalendarEvents: ( events, { deltaToken = 'mock-calendar-delta-token', removedEventIds = [] } = {}, ) => httpMock.use( ...microsoftCalendarEventsHandlers(events, deltaToken, removedEventIds), ), moveMessageToFolder: (messageId, targetFolderId) => { const message = recordFolderRemoval(messageId); message.parentFolderId = targetFolderId; }, deleteMessage: (messageId) => { const message = recordFolderRemoval(messageId); messages.splice(messages.indexOf(message), 1); }, failMessageFetch: (messageId) => httpMock.use( ...microsoftMailboxHandlers( folderStore, messages, removedMessageIdsByFolderId, { unfetchableMessageIds: [messageId] }, ), ), failSubscriptionRenewal: () => httpMock.use( ...microsoftWebhookSubscriptionHandlers(subscriptionStore, { renewalFails: true, }), ), // The driver issues the per-folder delta requests inside a /$batch POST, so // the failure has to come back as the status on each sub-response rather // than on the delta route itself. failMessageDelta: (failure) => httpMock.use( http.post('*/$batch', async ({ request }) => { const { requests } = (await request.json()) as { requests: { id: string }[]; }; return HttpResponse.json({ responses: requests.map(({ id }) => ({ id, status: failure.status, body: { error: { code: failure.code, message: failure.message }, }, })), }); }), http.get('*/messages/delta', () => microsoftGraphErrorResponse(failure), ), ), failCalendarDelta: (failure) => httpMock.use( http.get('*/me/calendar/events/delta', () => microsoftGraphErrorResponse(failure), ), ), declineTokenRefresh: () => httpMock.use( http.post(MICROSOFT_TOKEN_URL, () => HttpResponse.json( { error: 'invalid_grant', error_description: 'AADSTS50173: The provided grant has expired due to it being revoked', }, { status: 400 }, ), ), ), }; };