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:
Guillim
2025-04-09 17:57:43 +02:00
committed by GitHub
parent e23688cb41
commit 9f4e8c046f
5 changed files with 851 additions and 3 deletions

View File

@ -157,7 +157,6 @@ export class MessagingMessageListFetchJob {
await this.messagingFullMessageListFetchService.processMessageListFetch( await this.messagingFullMessageListFetchService.processMessageListFetch(
messageChannel, messageChannel,
messageChannel.connectedAccount,
workspaceId, workspaceId,
); );

View File

@ -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]);
});
});

View File

@ -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 {

View File

@ -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);
});
});

View File

@ -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: [],
},
);
});
});