mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
321 lines
11 KiB
TypeScript
321 lines
11 KiB
TypeScript
/*
|
|
* Copyright © 2024 Hexastack. All rights reserved.
|
|
*
|
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
|
*/
|
|
|
|
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
import { MongooseModule } from '@nestjs/mongoose';
|
|
import { Test } from '@nestjs/testing';
|
|
|
|
import { AttachmentRepository } from '@/attachment/repositories/attachment.repository';
|
|
import { AttachmentModel } from '@/attachment/schemas/attachment.schema';
|
|
import { AttachmentService } from '@/attachment/services/attachment.service';
|
|
import { ChannelService } from '@/channel/channel.service';
|
|
import { ContentTypeRepository } from '@/cms/repositories/content-type.repository';
|
|
import { ContentRepository } from '@/cms/repositories/content.repository';
|
|
import { MenuRepository } from '@/cms/repositories/menu.repository';
|
|
import { ContentTypeModel } from '@/cms/schemas/content-type.schema';
|
|
import { ContentModel } from '@/cms/schemas/content.schema';
|
|
import { MenuModel } from '@/cms/schemas/menu.schema';
|
|
import { ContentTypeService } from '@/cms/services/content-type.service';
|
|
import { ContentService } from '@/cms/services/content.service';
|
|
import { MenuService } from '@/cms/services/menu.service';
|
|
import { offlineEventText } from '@/extensions/channels/offline/__test__/events.mock';
|
|
import OfflineHandler from '@/extensions/channels/offline/index.channel';
|
|
import OfflineEventWrapper from '@/extensions/channels/offline/wrapper';
|
|
import { HelperService } from '@/helper/helper.service';
|
|
import { LanguageRepository } from '@/i18n/repositories/language.repository';
|
|
import { LanguageModel } from '@/i18n/schemas/language.schema';
|
|
import { I18nService } from '@/i18n/services/i18n.service';
|
|
import { LanguageService } from '@/i18n/services/language.service';
|
|
import { LoggerService } from '@/logger/logger.service';
|
|
import { PluginService } from '@/plugins/plugins.service';
|
|
import { SettingService } from '@/setting/services/setting.service';
|
|
import { installBlockFixtures } from '@/utils/test/fixtures/block';
|
|
import { installContentFixtures } from '@/utils/test/fixtures/content';
|
|
import { installSubscriberFixtures } from '@/utils/test/fixtures/subscriber';
|
|
import {
|
|
closeInMongodConnection,
|
|
rootMongooseTestModule,
|
|
} from '@/utils/test/test';
|
|
import { SocketEventDispatcherService } from '@/websocket/services/socket-event-dispatcher.service';
|
|
import { WebsocketGateway } from '@/websocket/websocket.gateway';
|
|
|
|
import { BlockRepository } from '../repositories/block.repository';
|
|
import { ContextVarRepository } from '../repositories/context-var.repository';
|
|
import { ConversationRepository } from '../repositories/conversation.repository';
|
|
import { MessageRepository } from '../repositories/message.repository';
|
|
import { SubscriberRepository } from '../repositories/subscriber.repository';
|
|
import { BlockFull, BlockModel } from '../schemas/block.schema';
|
|
import { CategoryModel } from '../schemas/category.schema';
|
|
import { ContextVarModel } from '../schemas/context-var.schema';
|
|
import {
|
|
Conversation,
|
|
ConversationFull,
|
|
ConversationModel,
|
|
} from '../schemas/conversation.schema';
|
|
import { LabelModel } from '../schemas/label.schema';
|
|
import { MessageModel } from '../schemas/message.schema';
|
|
import { SubscriberModel } from '../schemas/subscriber.schema';
|
|
|
|
import { CategoryRepository } from './../repositories/category.repository';
|
|
import { BlockService } from './block.service';
|
|
import { BotService } from './bot.service';
|
|
import { CategoryService } from './category.service';
|
|
import { ContextVarService } from './context-var.service';
|
|
import { ConversationService } from './conversation.service';
|
|
import { MessageService } from './message.service';
|
|
import { SubscriberService } from './subscriber.service';
|
|
|
|
describe('BlockService', () => {
|
|
let blockService: BlockService;
|
|
let subscriberService: SubscriberService;
|
|
let botService: BotService;
|
|
let handler: OfflineHandler;
|
|
let eventEmitter: EventEmitter2;
|
|
|
|
beforeAll(async () => {
|
|
const module = await Test.createTestingModule({
|
|
imports: [
|
|
rootMongooseTestModule(async () => {
|
|
await installSubscriberFixtures();
|
|
await installContentFixtures();
|
|
await installBlockFixtures();
|
|
}),
|
|
MongooseModule.forFeature([
|
|
BlockModel,
|
|
CategoryModel,
|
|
ContentTypeModel,
|
|
ContentModel,
|
|
AttachmentModel,
|
|
LabelModel,
|
|
ConversationModel,
|
|
SubscriberModel,
|
|
MessageModel,
|
|
MenuModel,
|
|
ContextVarModel,
|
|
LanguageModel,
|
|
]),
|
|
],
|
|
providers: [
|
|
EventEmitter2,
|
|
BlockRepository,
|
|
CategoryRepository,
|
|
WebsocketGateway,
|
|
SocketEventDispatcherService,
|
|
ConversationRepository,
|
|
ContentTypeRepository,
|
|
ContentRepository,
|
|
AttachmentRepository,
|
|
SubscriberRepository,
|
|
MessageRepository,
|
|
MenuRepository,
|
|
LanguageRepository,
|
|
BlockService,
|
|
CategoryService,
|
|
ContentTypeService,
|
|
ContentService,
|
|
AttachmentService,
|
|
SubscriberService,
|
|
ConversationService,
|
|
BotService,
|
|
ChannelService,
|
|
MessageService,
|
|
MenuService,
|
|
OfflineHandler,
|
|
ContextVarService,
|
|
ContextVarRepository,
|
|
LanguageService,
|
|
{
|
|
provide: HelperService,
|
|
useValue: {},
|
|
},
|
|
{
|
|
provide: PluginService,
|
|
useValue: {},
|
|
},
|
|
LoggerService,
|
|
{
|
|
provide: I18nService,
|
|
useValue: {
|
|
t: jest.fn().mockImplementation((t) => t),
|
|
},
|
|
},
|
|
{
|
|
provide: SettingService,
|
|
useValue: {
|
|
getConfig: jest.fn(() => ({
|
|
chatbot: { lang: { default: 'fr' } },
|
|
})),
|
|
getSettings: jest.fn(() => ({
|
|
contact: { company_name: 'Your company name' },
|
|
})),
|
|
},
|
|
},
|
|
{
|
|
provide: CACHE_MANAGER,
|
|
useValue: {
|
|
del: jest.fn(),
|
|
get: jest.fn(),
|
|
set: jest.fn(),
|
|
},
|
|
},
|
|
],
|
|
}).compile();
|
|
subscriberService = module.get<SubscriberService>(SubscriberService);
|
|
botService = module.get<BotService>(BotService);
|
|
blockService = module.get<BlockService>(BlockService);
|
|
eventEmitter = module.get<EventEmitter2>(EventEmitter2);
|
|
handler = module.get<OfflineHandler>(OfflineHandler);
|
|
});
|
|
|
|
afterEach(jest.clearAllMocks);
|
|
afterAll(closeInMongodConnection);
|
|
|
|
it('should start a conversation', async () => {
|
|
const triggeredEvents: any[] = [];
|
|
|
|
eventEmitter.on('hook:stats:entry', (...args) => {
|
|
triggeredEvents.push(args);
|
|
});
|
|
|
|
const event = new OfflineEventWrapper(handler, offlineEventText, {
|
|
isSocket: false,
|
|
ipAddress: '1.1.1.1',
|
|
});
|
|
|
|
const [block] = await blockService.findAndPopulate({ patterns: ['Hi'] });
|
|
const offlineSubscriber = await subscriberService.findOne({
|
|
foreign_id: 'foreign-id-offline-1',
|
|
});
|
|
|
|
event.setSender(offlineSubscriber);
|
|
|
|
let hasBotSpoken = false;
|
|
const clearMock = jest
|
|
.spyOn(botService, 'findBlockAndSendReply')
|
|
.mockImplementation(
|
|
(
|
|
actualEvent: OfflineEventWrapper,
|
|
actualConversation: Conversation,
|
|
actualBlock: BlockFull,
|
|
isFallback: boolean,
|
|
) => {
|
|
expect(actualConversation).toEqualPayload({
|
|
sender: offlineSubscriber.id,
|
|
active: true,
|
|
next: [],
|
|
context: {
|
|
user: {
|
|
first_name: offlineSubscriber.first_name,
|
|
last_name: offlineSubscriber.last_name,
|
|
language: 'en',
|
|
id: offlineSubscriber.id,
|
|
},
|
|
user_location: {
|
|
lat: 0,
|
|
lon: 0,
|
|
},
|
|
vars: {},
|
|
nlp: null,
|
|
payload: null,
|
|
attempt: 0,
|
|
channel: 'offline',
|
|
text: offlineEventText.data.text,
|
|
},
|
|
});
|
|
expect(actualEvent).toEqual(event);
|
|
expect(actualBlock).toEqual(block);
|
|
expect(isFallback).toEqual(false);
|
|
hasBotSpoken = true;
|
|
},
|
|
);
|
|
|
|
await botService.startConversation(event, block);
|
|
expect(hasBotSpoken).toEqual(true);
|
|
expect(triggeredEvents).toEqual([
|
|
['popular', 'hasNextBlocks'],
|
|
['new_conversations', 'New conversations'],
|
|
]);
|
|
clearMock.mockClear();
|
|
});
|
|
|
|
it('should capture a conversation', async () => {
|
|
const triggeredEvents: any[] = [];
|
|
|
|
eventEmitter.on('hook:stats:entry', (...args) => {
|
|
triggeredEvents.push(args);
|
|
});
|
|
|
|
const event = new OfflineEventWrapper(handler, offlineEventText, {
|
|
isSocket: false,
|
|
ipAddress: '1.1.1.1',
|
|
});
|
|
const offlineSubscriber = await subscriberService.findOne({
|
|
foreign_id: 'foreign-id-offline-1',
|
|
});
|
|
event.setSender(offlineSubscriber);
|
|
|
|
const clearMock = jest
|
|
.spyOn(botService, 'handleIncomingMessage')
|
|
.mockImplementation(
|
|
async (
|
|
actualConversation: ConversationFull,
|
|
event: OfflineEventWrapper,
|
|
) => {
|
|
expect(actualConversation).toEqualPayload({
|
|
next: [],
|
|
sender: offlineSubscriber,
|
|
active: true,
|
|
context: {
|
|
user: {
|
|
first_name: offlineSubscriber.first_name,
|
|
last_name: offlineSubscriber.last_name,
|
|
language: 'en',
|
|
id: offlineSubscriber.id,
|
|
},
|
|
user_location: { lat: 0, lon: 0 },
|
|
vars: {},
|
|
nlp: null,
|
|
payload: null,
|
|
attempt: 0,
|
|
channel: 'offline',
|
|
text: offlineEventText.data.text,
|
|
},
|
|
});
|
|
expect(event).toEqual(event);
|
|
return true;
|
|
},
|
|
);
|
|
const captured = await botService.processConversationMessage(event);
|
|
expect(captured).toBe(true);
|
|
expect(triggeredEvents).toEqual([
|
|
['existing_conversations', 'Existing conversations'],
|
|
]);
|
|
clearMock.mockClear();
|
|
});
|
|
|
|
it('has no active conversation', async () => {
|
|
const triggeredEvents: any[] = [];
|
|
eventEmitter.on('hook:stats:entry', (...args) => {
|
|
triggeredEvents.push(args);
|
|
});
|
|
const event = new OfflineEventWrapper(handler, offlineEventText, {
|
|
isSocket: false,
|
|
ipAddress: '1.1.1.1',
|
|
});
|
|
const offlineSubscriber = await subscriberService.findOne({
|
|
foreign_id: 'foreign-id-offline-2',
|
|
});
|
|
event.setSender(offlineSubscriber);
|
|
const captured = await botService.processConversationMessage(event);
|
|
|
|
expect(captured).toBe(false);
|
|
expect(triggeredEvents).toEqual([]);
|
|
});
|
|
});
|