Files
twenty/packages/twenty-server/test/integration/google/messaging/stale-sync.integration-spec.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

90 lines
3.1 KiB
TypeScript

import {
ConnectedAccountProvider,
MessageChannelSyncStage,
} from 'twenty-shared/types';
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
import { MessagingOngoingStaleCronJob } from 'src/modules/messaging/message-import-manager/crons/jobs/messaging-ongoing-stale.cron.job';
import { setupGoogleMock } from 'test/integration/google/mocks/setup-google-mock.util';
import { connectMessagingAccount } from 'test/integration/utils/connect-messaging-account.util';
import { getCoreRepository } from 'test/integration/utils/get-core-repository.util';
import { queryMessageChannel } from 'test/integration/utils/query-messaging.util';
import { runSyncCron } from 'test/integration/utils/run-sync-cron.util';
const STALE_HANDLE = 'messaging-stale-sync@apple.dev';
const RECENT_HANDLE = 'messaging-recent-sync@apple.dev';
const STALE_STARTED_AT = new Date(Date.now() - 31 * 60 * 1000);
const RECENT_STARTED_AT = new Date(Date.now() - 60 * 1000);
describe('Messaging stale-sync recovery (integration)', () => {
const gmail = setupGoogleMock({ handle: STALE_HANDLE });
let staleChannel: Awaited<ReturnType<typeof connectMessagingAccount>>;
let recentChannel: Awaited<ReturnType<typeof connectMessagingAccount>>;
beforeAll(async () => {
staleChannel = await connectMessagingAccount({
provider: ConnectedAccountProvider.GOOGLE,
handle: STALE_HANDLE,
});
gmail.actAsAccount(RECENT_HANDLE);
recentChannel = await connectMessagingAccount({
provider: ConnectedAccountProvider.GOOGLE,
handle: RECENT_HANDLE,
});
for (const connectedChannel of [staleChannel, recentChannel]) {
const channelState = await queryMessageChannel(connectedChannel);
expect(channelState.syncStage).toBe(
MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
);
}
}, 120000);
afterAll(async () => {
await staleChannel?.cleanup().catch(() => undefined);
await recentChannel?.cleanup().catch(() => undefined);
});
it('resets a stale ongoing channel to pending and leaves a recent one running', async () => {
const messageChannelRepository =
getCoreRepository<MessageChannelEntity>(MessageChannelEntity);
await messageChannelRepository.update(
{ id: staleChannel.channelId },
{
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING,
syncStageStartedAt: STALE_STARTED_AT,
},
);
await messageChannelRepository.update(
{ id: recentChannel.channelId },
{
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING,
syncStageStartedAt: RECENT_STARTED_AT,
},
);
await runSyncCron(MessagingOngoingStaleCronJob);
const staleChannelAfter = await queryMessageChannel(staleChannel);
expect(staleChannelAfter.syncStage).toBe(
MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
);
expect(staleChannelAfter.syncStageStartedAt).toBeNull();
const recentChannelAfter = await queryMessageChannel(recentChannel);
expect(recentChannelAfter.syncStage).toBe(
MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING,
);
}, 60000);
});