Files
twenty/packages/twenty-server/test/integration/utils/connect-messaging-account.util.ts
T
neo773 d99e6db93d test(messaging): messaging and calendar sync integration suites (#22567)
13 integration suites driving the real sync pipeline end to end — OAuth
connect via the actual `/auth/google-apis/get-access-token` /
`microsoft-apis` callbacks (transient token + mocked provider token
exchange), real queue workers, provider APIs mocked at the HTTP layer
with msw.

**Messaging (8):** Gmail list fetch + import, Gmail folder discovery,
Microsoft folder discovery, history-based incremental sync, stale-sync
recovery, sync failure lifecycle (429 throttle → exhaustion → relaunch;
declined refresh token → insufficient permissions), token refresh,
connected-account cleanup cascade.

**Calendar (5):** Google events import (full + sync-token incremental),
Microsoft events import (delta fetch + import), stale-sync recovery,
failure lifecycle, cleanup cascade.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22567?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-07 18:38:05 +05:30

110 lines
3.1 KiB
TypeScript

import gql from 'graphql-tag';
import request from 'supertest';
import {
CalendarChannelVisibility,
ConnectedAccountProvider,
MessageChannelVisibility,
} from 'twenty-shared/types';
import {
deleteConnectedAccount,
getDataOrThrow,
queryCalendarChannels,
queryMessageChannels,
} from 'test/integration/utils/query-messaging.util';
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
import { waitForAllJobsToFinish } from 'test/integration/utils/wait-for-all-jobs-to-finish.util';
type ConnectMessagingAccountInput = {
provider: ConnectedAccountProvider;
handle: string;
skipChannelConfiguration?: boolean;
};
type ConnectMessagingAccountResult = {
channelId: string;
calendarChannelId: string;
connectedAccountId: string;
handle: string;
cleanup: () => Promise<void>;
};
const OAUTH_CALLBACK_PATH: Partial<Record<ConnectedAccountProvider, string>> = {
[ConnectedAccountProvider.GOOGLE]: '/auth/google-apis/get-access-token',
[ConnectedAccountProvider.MICROSOFT]: '/auth/microsoft-apis/get-access-token',
};
const generateTransientToken = async (): Promise<string> => {
const response = await makeMetadataAPIRequest({
query: gql`
mutation GenerateTransientToken {
generateTransientToken {
transientToken {
token
}
}
}
`,
});
const data = getDataOrThrow(response) as {
generateTransientToken: { transientToken: { token: string } };
};
return data.generateTransientToken.transientToken.token;
};
export const connectMessagingAccount = async ({
provider,
handle,
skipChannelConfiguration = true,
}: ConnectMessagingAccountInput): Promise<ConnectMessagingAccountResult> => {
const callbackPath = OAUTH_CALLBACK_PATH[provider];
if (!callbackPath) {
throw new Error(`Unsupported OAuth provider: ${provider}`);
}
const state = JSON.stringify({
transientToken: await generateTransientToken(),
messageVisibility: MessageChannelVisibility.SHARE_EVERYTHING,
calendarVisibility: CalendarChannelVisibility.SHARE_EVERYTHING,
skipMessageChannelConfiguration: skipChannelConfiguration,
});
const callbackResponse = await request(`http://localhost:${APP_PORT}`)
.get(callbackPath)
.query({ code: 'mock-authorization-code', state });
await waitForAllJobsToFinish();
const connectedChannel = (await queryMessageChannels()).find(
(channel) => channel.handle === handle,
);
if (!connectedChannel) {
throw new Error(
`OAuth connect for ${provider} created no message channel for ${handle} (callback redirected to ${callbackResponse.headers.location})`,
);
}
const [calendarChannel] = await queryCalendarChannels(
connectedChannel.connectedAccountId,
);
if (!calendarChannel) {
throw new Error(
`OAuth connect for ${provider} created no calendar channel for ${handle}`,
);
}
return {
channelId: connectedChannel.id,
calendarChannelId: calendarChannel.id,
connectedAccountId: connectedChannel.connectedAccountId,
handle,
cleanup: () => deleteConnectedAccount(connectedChannel.connectedAccountId),
};
};