Messaging-unit-tests (#11467)
# Unit test on the Messaging Module Initially the issue was to create integration test for the messaging module but after speaking with the core team, we decided to go for an easier implementation: unit test only, We decided to focus our test on three main components of the module : - message list - message import - message save & create contact Fixes https://github.com/twentyhq/core-team-issues/issues/56
This commit is contained in:
@ -157,7 +157,6 @@ export class MessagingMessageListFetchJob {
|
|||||||
|
|
||||||
await this.messagingFullMessageListFetchService.processMessageListFetch(
|
await this.messagingFullMessageListFetchService.processMessageListFetch(
|
||||||
messageChannel,
|
messageChannel,
|
||||||
messageChannel.connectedAccount,
|
|
||||||
workspaceId,
|
workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,239 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
|
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||||
|
|
||||||
|
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
|
||||||
|
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
|
||||||
|
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||||
|
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||||
|
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||||
|
import { MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||||
|
import { MessagingMessageCleanerService } from 'src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service';
|
||||||
|
import { MessagingCursorService } from 'src/modules/messaging/message-import-manager/services/messaging-cursor.service';
|
||||||
|
import { MessagingFullMessageListFetchService } from 'src/modules/messaging/message-import-manager/services/messaging-full-message-list-fetch.service';
|
||||||
|
import { MessagingGetMessageListService } from 'src/modules/messaging/message-import-manager/services/messaging-get-message-list.service';
|
||||||
|
import { MessageImportExceptionHandlerService } from 'src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service';
|
||||||
|
|
||||||
|
describe('MessagingFullMessageListFetchService', () => {
|
||||||
|
let messagingFullMessageListFetchService: MessagingFullMessageListFetchService;
|
||||||
|
let messagingGetMessageListService: MessagingGetMessageListService;
|
||||||
|
let messageChannelSyncStatusService: MessageChannelSyncStatusService;
|
||||||
|
let twentyORMManager: TwentyORMManager;
|
||||||
|
let messagingCursorService: MessagingCursorService;
|
||||||
|
|
||||||
|
let mockMicrosoftMessageChannel: MessageChannelWorkspaceEntity;
|
||||||
|
let mockGoogleMessageChannel: MessageChannelWorkspaceEntity;
|
||||||
|
|
||||||
|
const workspaceId = 'workspace-id';
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
mockMicrosoftMessageChannel = {
|
||||||
|
id: 'microsoft-message-channel-id',
|
||||||
|
connectedAccount: {
|
||||||
|
id: 'microsoft-connected-account-id',
|
||||||
|
provider: ConnectedAccountProvider.MICROSOFT,
|
||||||
|
handle: 'test@microsoft.com',
|
||||||
|
refreshToken: 'refresh-token',
|
||||||
|
handleAliases: '',
|
||||||
|
},
|
||||||
|
messageFolders: [
|
||||||
|
{
|
||||||
|
id: 'inbox-folder-id',
|
||||||
|
name: 'inbox',
|
||||||
|
syncCursor: 'inbox-sync-cursor',
|
||||||
|
messageChannelId: 'microsoft-message-channel-id',
|
||||||
|
} as MessageFolderWorkspaceEntity,
|
||||||
|
],
|
||||||
|
} as MessageChannelWorkspaceEntity;
|
||||||
|
|
||||||
|
mockGoogleMessageChannel = {
|
||||||
|
id: 'google-message-channel-id',
|
||||||
|
connectedAccount: {
|
||||||
|
id: 'google-connected-account-id',
|
||||||
|
provider: ConnectedAccountProvider.GOOGLE,
|
||||||
|
handle: 'test@gmail.com',
|
||||||
|
refreshToken: 'google-refresh-token',
|
||||||
|
handleAliases: '',
|
||||||
|
},
|
||||||
|
syncCursor: 'google-sync-cursor',
|
||||||
|
} as MessageChannelWorkspaceEntity;
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const mockMessageChannelMessageAssociationRepository = {
|
||||||
|
find: jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue([
|
||||||
|
{ messageExternalId: 'external-id-existing-message-1' },
|
||||||
|
{ messageExternalId: 'external-id-existing-message-2' },
|
||||||
|
]),
|
||||||
|
delete: jest.fn().mockResolvedValue(undefined),
|
||||||
|
};
|
||||||
|
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
MessagingFullMessageListFetchService,
|
||||||
|
{
|
||||||
|
provide: CacheStorageNamespace.ModuleMessaging,
|
||||||
|
useValue: {
|
||||||
|
setAdd: jest.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: MessagingGetMessageListService,
|
||||||
|
useValue: {
|
||||||
|
getFullMessageLists: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation((messageChannel) => {
|
||||||
|
if (
|
||||||
|
messageChannel.connectedAccount.provider ===
|
||||||
|
ConnectedAccountProvider.GOOGLE
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
messageExternalIds: [
|
||||||
|
'external-id-existing-message-1',
|
||||||
|
'external-id-google-message-1',
|
||||||
|
'external-id-google-message-2',
|
||||||
|
],
|
||||||
|
nextSyncCursor: 'new-google-history-id',
|
||||||
|
folderId: undefined,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
messageExternalIds: [
|
||||||
|
'external-id-existing-message-1',
|
||||||
|
'external-id-new-message-1',
|
||||||
|
'external-id-new-message-2',
|
||||||
|
],
|
||||||
|
nextSyncCursor: 'new-sync-cursor',
|
||||||
|
folderId: 'inbox-folder-id',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: MessageChannelSyncStatusService,
|
||||||
|
useValue: {
|
||||||
|
markAsMessagesListFetchOngoing: jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue(undefined),
|
||||||
|
scheduleMessagesImport: jest.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: TwentyORMManager,
|
||||||
|
useValue: {
|
||||||
|
getRepository: jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue(
|
||||||
|
mockMessageChannelMessageAssociationRepository,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: MessagingCursorService,
|
||||||
|
useValue: {
|
||||||
|
updateCursor: jest.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: CacheStorageService,
|
||||||
|
useValue: {
|
||||||
|
setAdd: jest.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: MessageImportExceptionHandlerService,
|
||||||
|
useValue: {
|
||||||
|
handleDriverException: jest.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: MessagingMessageCleanerService,
|
||||||
|
useValue: {
|
||||||
|
cleanWorkspaceThreads: jest.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
messagingFullMessageListFetchService =
|
||||||
|
module.get<MessagingFullMessageListFetchService>(
|
||||||
|
MessagingFullMessageListFetchService,
|
||||||
|
);
|
||||||
|
messagingGetMessageListService = module.get<MessagingGetMessageListService>(
|
||||||
|
MessagingGetMessageListService,
|
||||||
|
);
|
||||||
|
messageChannelSyncStatusService =
|
||||||
|
module.get<MessageChannelSyncStatusService>(
|
||||||
|
MessageChannelSyncStatusService,
|
||||||
|
);
|
||||||
|
twentyORMManager = module.get<TwentyORMManager>(TwentyORMManager);
|
||||||
|
messagingCursorService = module.get<MessagingCursorService>(
|
||||||
|
MessagingCursorService,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process Microsoft message list fetch correctly', async () => {
|
||||||
|
await messagingFullMessageListFetchService.processMessageListFetch(
|
||||||
|
mockMicrosoftMessageChannel,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
messageChannelSyncStatusService.markAsMessagesListFetchOngoing,
|
||||||
|
).toHaveBeenCalledWith([mockMicrosoftMessageChannel.id]);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
messagingGetMessageListService.getFullMessageLists,
|
||||||
|
).toHaveBeenCalledWith(mockMicrosoftMessageChannel);
|
||||||
|
|
||||||
|
expect(twentyORMManager.getRepository).toHaveBeenCalledWith(
|
||||||
|
'messageChannelMessageAssociation',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(messagingCursorService.updateCursor).toHaveBeenCalledWith(
|
||||||
|
mockMicrosoftMessageChannel,
|
||||||
|
'new-sync-cursor',
|
||||||
|
'inbox-folder-id',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
messageChannelSyncStatusService.scheduleMessagesImport,
|
||||||
|
).toHaveBeenCalledWith([mockMicrosoftMessageChannel.id]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process Google message list fetch correctly', async () => {
|
||||||
|
await messagingFullMessageListFetchService.processMessageListFetch(
|
||||||
|
mockGoogleMessageChannel,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
messageChannelSyncStatusService.markAsMessagesListFetchOngoing,
|
||||||
|
).toHaveBeenCalledWith([mockGoogleMessageChannel.id]);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
messagingGetMessageListService.getFullMessageLists,
|
||||||
|
).toHaveBeenCalledWith(mockGoogleMessageChannel);
|
||||||
|
|
||||||
|
expect(twentyORMManager.getRepository).toHaveBeenCalledWith(
|
||||||
|
'messageChannelMessageAssociation',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(messagingCursorService.updateCursor).toHaveBeenCalledWith(
|
||||||
|
mockGoogleMessageChannel,
|
||||||
|
'new-google-history-id',
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
messageChannelSyncStatusService.scheduleMessagesImport,
|
||||||
|
).toHaveBeenCalledWith([mockGoogleMessageChannel.id]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -6,7 +6,6 @@ import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decora
|
|||||||
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
|
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
|
||||||
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
|
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
|
||||||
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||||
import { MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity';
|
import { MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity';
|
||||||
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||||
@ -32,7 +31,6 @@ export class MessagingFullMessageListFetchService {
|
|||||||
|
|
||||||
public async processMessageListFetch(
|
public async processMessageListFetch(
|
||||||
messageChannel: MessageChannelWorkspaceEntity,
|
messageChannel: MessageChannelWorkspaceEntity,
|
||||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -0,0 +1,284 @@
|
|||||||
|
import { Logger, Provider } from '@nestjs/common';
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
|
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||||
|
|
||||||
|
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
|
||||||
|
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
|
||||||
|
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||||
|
import { BlocklistRepository } from 'src/modules/blocklist/repositories/blocklist.repository';
|
||||||
|
import { EmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/services/email-alias-manager.service';
|
||||||
|
import { ConnectedAccountRefreshTokensService } from 'src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service';
|
||||||
|
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||||
|
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||||
|
import {
|
||||||
|
MessageChannelSyncStage,
|
||||||
|
MessageChannelWorkspaceEntity,
|
||||||
|
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||||
|
import { MESSAGING_GMAIL_USERS_MESSAGES_GET_BATCH_SIZE } from 'src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-users-messages-get-batch-size.constant';
|
||||||
|
import { MessagingGetMessagesService } from 'src/modules/messaging/message-import-manager/services/messaging-get-messages.service';
|
||||||
|
import { MessageImportExceptionHandlerService } from 'src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service';
|
||||||
|
import { MessagingMessagesImportService } from 'src/modules/messaging/message-import-manager/services/messaging-messages-import.service';
|
||||||
|
import { MessagingSaveMessagesAndEnqueueContactCreationService } from 'src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service';
|
||||||
|
import { MessagingTelemetryService } from 'src/modules/messaging/monitoring/services/messaging-telemetry.service';
|
||||||
|
|
||||||
|
describe('MessagingMessagesImportService', () => {
|
||||||
|
let service: MessagingMessagesImportService;
|
||||||
|
let messageChannelSyncStatusService: MessageChannelSyncStatusService;
|
||||||
|
let connectedAccountRefreshTokensService: ConnectedAccountRefreshTokensService;
|
||||||
|
let emailAliasManagerService: EmailAliasManagerService;
|
||||||
|
let messagingGetMessagesService: MessagingGetMessagesService;
|
||||||
|
let saveMessagesService: MessagingSaveMessagesAndEnqueueContactCreationService;
|
||||||
|
|
||||||
|
const workspaceId = 'workspace-id';
|
||||||
|
let mockMessageChannel: MessageChannelWorkspaceEntity;
|
||||||
|
let mockConnectedAccount: ConnectedAccountWorkspaceEntity;
|
||||||
|
let providersBase: Provider[];
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
mockConnectedAccount = {
|
||||||
|
id: 'connected-account-id',
|
||||||
|
provider: ConnectedAccountProvider.GOOGLE,
|
||||||
|
handle: 'test@gmail.com',
|
||||||
|
refreshToken: 'refresh-token',
|
||||||
|
accessToken: 'old-access-token',
|
||||||
|
accountOwnerId: 'account-owner-id',
|
||||||
|
handleAliases: 'alias1@gmail.com,alias2@gmail.com',
|
||||||
|
} as ConnectedAccountWorkspaceEntity;
|
||||||
|
|
||||||
|
mockMessageChannel = {
|
||||||
|
id: 'message-channel-id',
|
||||||
|
syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_PENDING,
|
||||||
|
connectedAccountId: mockConnectedAccount.id,
|
||||||
|
handle: 'test@gmail.com',
|
||||||
|
} as MessageChannelWorkspaceEntity;
|
||||||
|
|
||||||
|
providersBase = [
|
||||||
|
MessagingMessagesImportService,
|
||||||
|
{
|
||||||
|
provide: CacheStorageService,
|
||||||
|
useValue: {
|
||||||
|
setAdd: jest.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: MessageChannelSyncStatusService,
|
||||||
|
useValue: {
|
||||||
|
markAsMessagesImportOngoing: jest.fn().mockResolvedValue(undefined),
|
||||||
|
markAsCompletedAndSchedulePartialMessageListFetch: jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue(undefined),
|
||||||
|
scheduleMessagesImport: jest.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ConnectedAccountRefreshTokensService,
|
||||||
|
useValue: {
|
||||||
|
refreshAndSaveTokens: jest.fn().mockResolvedValue('new-access-token'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: MessagingTelemetryService,
|
||||||
|
useValue: {
|
||||||
|
track: jest.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: 'BlocklistRepository',
|
||||||
|
useValue: {
|
||||||
|
getByWorkspaceMemberId: jest.fn().mockResolvedValue([]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: BlocklistRepository,
|
||||||
|
useValue: {
|
||||||
|
getByWorkspaceMemberId: jest.fn().mockResolvedValue([]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: EmailAliasManagerService,
|
||||||
|
useValue: {
|
||||||
|
refreshHandleAliases: jest.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: TwentyORMManager,
|
||||||
|
useValue: {
|
||||||
|
getRepository: jest.fn().mockResolvedValue({
|
||||||
|
update: jest.fn().mockResolvedValue(undefined),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: MessagingGetMessagesService,
|
||||||
|
useValue: {
|
||||||
|
getMessages: jest.fn().mockResolvedValue([
|
||||||
|
{
|
||||||
|
id: 'message-1',
|
||||||
|
from: 'sender@example.com',
|
||||||
|
to: 'test@gmail.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'message-2',
|
||||||
|
from: 'test@gmail.com',
|
||||||
|
to: 'recipient@example.com',
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: MessagingSaveMessagesAndEnqueueContactCreationService,
|
||||||
|
useValue: {
|
||||||
|
saveMessagesAndEnqueueContactCreation: jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: MessageImportExceptionHandlerService,
|
||||||
|
useValue: {
|
||||||
|
handleDriverException: jest.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
...providersBase,
|
||||||
|
{
|
||||||
|
provide: CacheStorageNamespace.ModuleMessaging,
|
||||||
|
useValue: {
|
||||||
|
setPop: jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue(['message-id-1', 'message-id-2']),
|
||||||
|
setAdd: jest.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.overrideProvider(Logger)
|
||||||
|
.useValue({ log: jest.fn() })
|
||||||
|
.compile();
|
||||||
|
|
||||||
|
service = module.get<MessagingMessagesImportService>(
|
||||||
|
MessagingMessagesImportService,
|
||||||
|
);
|
||||||
|
|
||||||
|
messageChannelSyncStatusService =
|
||||||
|
module.get<MessageChannelSyncStatusService>(
|
||||||
|
MessageChannelSyncStatusService,
|
||||||
|
);
|
||||||
|
connectedAccountRefreshTokensService =
|
||||||
|
module.get<ConnectedAccountRefreshTokensService>(
|
||||||
|
ConnectedAccountRefreshTokensService,
|
||||||
|
);
|
||||||
|
emailAliasManagerService = module.get<EmailAliasManagerService>(
|
||||||
|
EmailAliasManagerService,
|
||||||
|
);
|
||||||
|
messagingGetMessagesService = module.get<MessagingGetMessagesService>(
|
||||||
|
MessagingGetMessagesService,
|
||||||
|
);
|
||||||
|
|
||||||
|
saveMessagesService =
|
||||||
|
module.get<MessagingSaveMessagesAndEnqueueContactCreationService>(
|
||||||
|
MessagingSaveMessagesAndEnqueueContactCreationService,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fails if SyncStage is not MESSAGES_IMPORT_PENDING', async () => {
|
||||||
|
mockMessageChannel.syncStage =
|
||||||
|
MessageChannelSyncStage.PARTIAL_MESSAGE_LIST_FETCH_PENDING;
|
||||||
|
|
||||||
|
expect(
|
||||||
|
service.processMessageBatchImport(
|
||||||
|
mockMessageChannel,
|
||||||
|
mockConnectedAccount,
|
||||||
|
workspaceId,
|
||||||
|
),
|
||||||
|
).resolves.toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process message batch import successfully', async () => {
|
||||||
|
await service.processMessageBatchImport(
|
||||||
|
mockMessageChannel,
|
||||||
|
mockConnectedAccount,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
messageChannelSyncStatusService.markAsMessagesImportOngoing,
|
||||||
|
).toHaveBeenCalledWith([mockMessageChannel.id]);
|
||||||
|
expect(
|
||||||
|
connectedAccountRefreshTokensService.refreshAndSaveTokens,
|
||||||
|
).toHaveBeenCalledWith(mockConnectedAccount, workspaceId);
|
||||||
|
expect(emailAliasManagerService.refreshHandleAliases).toHaveBeenCalledWith(
|
||||||
|
mockConnectedAccount,
|
||||||
|
);
|
||||||
|
expect(messagingGetMessagesService.getMessages).toHaveBeenCalledWith(
|
||||||
|
['message-id-1', 'message-id-2'],
|
||||||
|
mockConnectedAccount,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
saveMessagesService.saveMessagesAndEnqueueContactCreation,
|
||||||
|
).toHaveBeenCalled();
|
||||||
|
expect(
|
||||||
|
messageChannelSyncStatusService.scheduleMessagesImport,
|
||||||
|
).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process message batch import of more than MESSAGING_GMAIL_USERS_MESSAGES_GET_BATCH_SIZE successfully', async () => {
|
||||||
|
const arrayMessagesBig = Array.from(
|
||||||
|
{ length: MESSAGING_GMAIL_USERS_MESSAGES_GET_BATCH_SIZE + 1 },
|
||||||
|
(_, index) => `message-id-${index + 1}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
...providersBase,
|
||||||
|
{
|
||||||
|
provide: CacheStorageNamespace.ModuleMessaging,
|
||||||
|
useValue: {
|
||||||
|
setPop: jest.fn().mockResolvedValue(arrayMessagesBig),
|
||||||
|
setAdd: jest.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.overrideProvider(Logger)
|
||||||
|
.useValue({ log: jest.fn() })
|
||||||
|
.compile();
|
||||||
|
|
||||||
|
service = module.get<MessagingMessagesImportService>(
|
||||||
|
MessagingMessagesImportService,
|
||||||
|
);
|
||||||
|
|
||||||
|
messageChannelSyncStatusService =
|
||||||
|
module.get<MessageChannelSyncStatusService>(
|
||||||
|
MessageChannelSyncStatusService,
|
||||||
|
);
|
||||||
|
connectedAccountRefreshTokensService =
|
||||||
|
module.get<ConnectedAccountRefreshTokensService>(
|
||||||
|
ConnectedAccountRefreshTokensService,
|
||||||
|
);
|
||||||
|
emailAliasManagerService = module.get<EmailAliasManagerService>(
|
||||||
|
EmailAliasManagerService,
|
||||||
|
);
|
||||||
|
messagingGetMessagesService = module.get<MessagingGetMessagesService>(
|
||||||
|
MessagingGetMessagesService,
|
||||||
|
);
|
||||||
|
|
||||||
|
saveMessagesService =
|
||||||
|
module.get<MessagingSaveMessagesAndEnqueueContactCreationService>(
|
||||||
|
MessagingSaveMessagesAndEnqueueContactCreationService,
|
||||||
|
);
|
||||||
|
|
||||||
|
await service.processMessageBatchImport(
|
||||||
|
mockMessageChannel,
|
||||||
|
mockConnectedAccount,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
messageChannelSyncStatusService.scheduleMessagesImport,
|
||||||
|
).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,328 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||||
|
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||||
|
import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-queue-token.util';
|
||||||
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
|
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||||
|
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||||
|
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||||
|
import { CreateCompanyAndContactJob } from 'src/modules/contact-creation-manager/jobs/create-company-and-contact.job';
|
||||||
|
import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum';
|
||||||
|
import {
|
||||||
|
MessageChannelContactAutoCreationPolicy,
|
||||||
|
MessageChannelWorkspaceEntity,
|
||||||
|
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||||
|
import { MessagingMessageService } from 'src/modules/messaging/message-import-manager/services/messaging-message.service';
|
||||||
|
import { MessagingSaveMessagesAndEnqueueContactCreationService } from 'src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service';
|
||||||
|
import { MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message';
|
||||||
|
import { MessagingMessageParticipantService } from 'src/modules/messaging/message-participant-manager/services/messaging-message-participant.service';
|
||||||
|
|
||||||
|
describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => {
|
||||||
|
let service: MessagingSaveMessagesAndEnqueueContactCreationService;
|
||||||
|
let messageQueueService: MessageQueueService;
|
||||||
|
let messageService: MessagingMessageService;
|
||||||
|
let messageParticipantService: MessagingMessageParticipantService;
|
||||||
|
|
||||||
|
let datasourceInstance: { transaction: jest.Mock };
|
||||||
|
|
||||||
|
const workspaceId = 'workspace-id';
|
||||||
|
|
||||||
|
const mockConnectedAccount: ConnectedAccountWorkspaceEntity = {
|
||||||
|
id: 'connected-account-id',
|
||||||
|
handle: 'test@example.com',
|
||||||
|
handleAliases: 'alias1@example.com,alias2@example.com',
|
||||||
|
} as ConnectedAccountWorkspaceEntity;
|
||||||
|
|
||||||
|
const mockMessageChannel: MessageChannelWorkspaceEntity = {
|
||||||
|
id: 'message-channel-id',
|
||||||
|
isContactAutoCreationEnabled: true,
|
||||||
|
contactAutoCreationPolicy:
|
||||||
|
MessageChannelContactAutoCreationPolicy.SENT_AND_RECEIVED,
|
||||||
|
excludeNonProfessionalEmails: true,
|
||||||
|
excludeGroupEmails: true,
|
||||||
|
} as MessageChannelWorkspaceEntity;
|
||||||
|
|
||||||
|
const mockMessages: MessageWithParticipants[] = [
|
||||||
|
{
|
||||||
|
externalId: 'message-1',
|
||||||
|
headerMessageId: 'header-message-id-1',
|
||||||
|
subject: 'Test Subject 1',
|
||||||
|
text: 'Test content 1',
|
||||||
|
receivedAt: new Date(),
|
||||||
|
attachments: [],
|
||||||
|
messageThreadExternalId: 'thread-1',
|
||||||
|
direction: MessageDirection.OUTGOING,
|
||||||
|
participants: [
|
||||||
|
{ role: 'from', handle: 'test@example.com', displayName: 'Test User' },
|
||||||
|
{ role: 'to', handle: 'contact@company.com', displayName: 'Contact' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
externalId: 'message-2',
|
||||||
|
headerMessageId: 'header-message-id-2',
|
||||||
|
subject: 'Test Subject 2',
|
||||||
|
text: 'Test content 2',
|
||||||
|
receivedAt: new Date(),
|
||||||
|
attachments: [],
|
||||||
|
messageThreadExternalId: 'thread-1',
|
||||||
|
direction: MessageDirection.INCOMING,
|
||||||
|
participants: [
|
||||||
|
{ role: 'from', handle: 'contact@company.com', displayName: 'Contact' },
|
||||||
|
{ role: 'to', handle: 'test@example.com', displayName: 'Test User' },
|
||||||
|
{ role: 'to', handle: 'personal@gmail.com', displayName: 'Personal' },
|
||||||
|
{
|
||||||
|
role: 'to',
|
||||||
|
handle: 'team@lists.company.com',
|
||||||
|
displayName: 'Group email',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
datasourceInstance = {
|
||||||
|
transaction: jest.fn().mockImplementation(async (callback) => {
|
||||||
|
return callback({});
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
MessagingSaveMessagesAndEnqueueContactCreationService,
|
||||||
|
{
|
||||||
|
provide: MessageQueueService,
|
||||||
|
useValue: {
|
||||||
|
add: jest.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: getQueueToken(MessageQueue.contactCreationQueue),
|
||||||
|
useValue: {
|
||||||
|
add: jest.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: WorkspaceEventEmitter,
|
||||||
|
useValue: {
|
||||||
|
emitDatabaseBatchEvent: jest.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(ObjectMetadataEntity, 'metadata'),
|
||||||
|
useValue: {
|
||||||
|
findOneOrFail: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: MessagingMessageService,
|
||||||
|
useValue: {
|
||||||
|
saveMessagesWithinTransaction: jest.fn().mockResolvedValue(
|
||||||
|
new Map([
|
||||||
|
['message-1', 'db-message-id-1'],
|
||||||
|
['message-2', 'db-message-id-2'],
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: MessagingMessageParticipantService,
|
||||||
|
useValue: {
|
||||||
|
saveMessageParticipants: jest.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: TwentyORMManager,
|
||||||
|
useValue: {
|
||||||
|
getDatasource: jest.fn().mockResolvedValue(datasourceInstance),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<MessagingSaveMessagesAndEnqueueContactCreationService>(
|
||||||
|
MessagingSaveMessagesAndEnqueueContactCreationService,
|
||||||
|
);
|
||||||
|
messageQueueService = module.get<MessageQueueService>(
|
||||||
|
getQueueToken(MessageQueue.contactCreationQueue),
|
||||||
|
);
|
||||||
|
messageService = module.get<MessagingMessageService>(
|
||||||
|
MessagingMessageService,
|
||||||
|
);
|
||||||
|
messageParticipantService = module.get<MessagingMessageParticipantService>(
|
||||||
|
MessagingMessageParticipantService,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should save messages and enqueue contact creation', async () => {
|
||||||
|
await service.saveMessagesAndEnqueueContactCreation(
|
||||||
|
mockMessages,
|
||||||
|
mockMessageChannel,
|
||||||
|
mockConnectedAccount,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(messageService.saveMessagesWithinTransaction).toHaveBeenCalledWith(
|
||||||
|
mockMessages,
|
||||||
|
mockMessageChannel.id,
|
||||||
|
expect.any(Object),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
messageParticipantService.saveMessageParticipants,
|
||||||
|
).toHaveBeenCalled();
|
||||||
|
expect(messageQueueService.add).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not enqueue contact creation when it is disabled', async () => {
|
||||||
|
await service.saveMessagesAndEnqueueContactCreation(
|
||||||
|
mockMessages,
|
||||||
|
{
|
||||||
|
...mockMessageChannel,
|
||||||
|
isContactAutoCreationEnabled: false,
|
||||||
|
},
|
||||||
|
mockConnectedAccount,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(messageService.saveMessagesWithinTransaction).toHaveBeenCalled();
|
||||||
|
expect(messageQueueService.add).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create external contacts', async () => {
|
||||||
|
await service.saveMessagesAndEnqueueContactCreation(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
...mockMessages[1],
|
||||||
|
participants: [
|
||||||
|
{
|
||||||
|
role: 'from',
|
||||||
|
handle: 'tim@apple.com',
|
||||||
|
displayName: 'participant email',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
mockMessageChannel,
|
||||||
|
mockConnectedAccount,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(messageQueueService.add).toHaveBeenCalledWith(
|
||||||
|
CreateCompanyAndContactJob.name,
|
||||||
|
{
|
||||||
|
workspaceId,
|
||||||
|
connectedAccount: mockConnectedAccount,
|
||||||
|
source: FieldActorSource.EMAIL,
|
||||||
|
contactsToCreate: [
|
||||||
|
{
|
||||||
|
handle: 'tim@apple.com',
|
||||||
|
displayName: 'participant email',
|
||||||
|
role: 'from',
|
||||||
|
shouldCreateContact: true,
|
||||||
|
messageId: 'db-message-id-2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create group emails contacts', async () => {
|
||||||
|
await service.saveMessagesAndEnqueueContactCreation(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
...mockMessages[0],
|
||||||
|
participants: [
|
||||||
|
{
|
||||||
|
role: 'from',
|
||||||
|
handle: 'contact@group.com',
|
||||||
|
displayName: 'participant that is the Connected Account',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
mockMessageChannel,
|
||||||
|
mockConnectedAccount,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(messageQueueService.add).toHaveBeenCalledWith(
|
||||||
|
CreateCompanyAndContactJob.name,
|
||||||
|
{
|
||||||
|
workspaceId,
|
||||||
|
connectedAccount: mockConnectedAccount,
|
||||||
|
source: FieldActorSource.EMAIL,
|
||||||
|
contactsToCreate: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create personal emails contacts', async () => {
|
||||||
|
await service.saveMessagesAndEnqueueContactCreation(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
...mockMessages[0],
|
||||||
|
participants: [
|
||||||
|
{
|
||||||
|
role: 'from',
|
||||||
|
handle: 'test@gmail.com',
|
||||||
|
displayName: 'participant personal email',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
mockMessageChannel,
|
||||||
|
mockConnectedAccount,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(messageQueueService.add).toHaveBeenCalledWith(
|
||||||
|
CreateCompanyAndContactJob.name,
|
||||||
|
{
|
||||||
|
workspaceId,
|
||||||
|
connectedAccount: mockConnectedAccount,
|
||||||
|
source: FieldActorSource.EMAIL,
|
||||||
|
contactsToCreate: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should not create contact if the participant is the connected account', async () => {
|
||||||
|
const mockMessagesWithConnectedAccount = [
|
||||||
|
{
|
||||||
|
...mockMessages[0],
|
||||||
|
participants: [
|
||||||
|
{
|
||||||
|
role: 'from',
|
||||||
|
handle: 'connected@account.com',
|
||||||
|
displayName: 'participant that is the Connected Account',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await service.saveMessagesAndEnqueueContactCreation(
|
||||||
|
mockMessagesWithConnectedAccount,
|
||||||
|
mockMessageChannel,
|
||||||
|
{
|
||||||
|
...mockConnectedAccount,
|
||||||
|
handle: 'connected@account.com',
|
||||||
|
},
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(messageQueueService.add).toHaveBeenCalledWith(
|
||||||
|
CreateCompanyAndContactJob.name,
|
||||||
|
{
|
||||||
|
workspaceId,
|
||||||
|
connectedAccount: {
|
||||||
|
...mockConnectedAccount,
|
||||||
|
handle: 'connected@account.com',
|
||||||
|
},
|
||||||
|
source: FieldActorSource.EMAIL,
|
||||||
|
contactsToCreate: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user